window.IAMManagement = (function() {
'use strict';
var users = [];
var currentUserKey = null;
var endpoints = {};
var csrfToken = '';
var iamLocked = false;
var policyModal = null;
var editUserModal = null;
var deleteUserModal = null;
var rotateSecretModal = null;
var currentRotateKey = null;
var currentEditKey = null;
var currentDeleteKey = null;
var ALL_S3_ACTIONS = ['list', 'read', 'write', 'delete', 'share', 'policy', 'replication', 'lifecycle', 'cors'];
var policyTemplates = {
full: [{ bucket: '*', actions: ['list', 'read', 'write', 'delete', 'share', 'policy', 'replication', 'lifecycle', 'cors', 'iam:*'] }],
readonly: [{ bucket: '*', actions: ['list', 'read'] }],
writer: [{ bucket: '*', actions: ['list', 'read', 'write'] }]
};
function isAdminUser(policies) {
if (!policies || !policies.length) return false;
return policies.some(function(p) {
return p.actions && (p.actions.indexOf('iam:*') >= 0 || p.actions.indexOf('*') >= 0);
});
}
function getPermissionLevel(actions) {
if (!actions || !actions.length) return 'Custom (0)';
if (actions.indexOf('*') >= 0) return 'Full Access';
if (actions.length >= ALL_S3_ACTIONS.length) {
var hasAll = ALL_S3_ACTIONS.every(function(a) { return actions.indexOf(a) >= 0; });
if (hasAll) return 'Full Access';
}
var has = function(a) { return actions.indexOf(a) >= 0; };
if (has('list') && has('read') && has('write') && has('delete')) return 'Read + Write + Delete';
if (has('list') && has('read') && has('write')) return 'Read + Write';
if (has('list') && has('read')) return 'Read Only';
return 'Custom (' + actions.length + ')';
}
function getBucketLabel(bucket) {
return bucket === '*' ? 'All Buckets' : bucket;
}
function init(config) {
users = config.users || [];
currentUserKey = config.currentUserKey || null;
endpoints = config.endpoints || {};
csrfToken = config.csrfToken || '';
iamLocked = config.iamLocked || false;
if (iamLocked) return;
initModals();
setupJsonAutoIndent();
setupCopyButtons();
setupPolicyEditor();
setupCreateUserModal();
setupEditUserModal();
setupDeleteUserModal();
setupRotateSecretModal();
setupFormHandlers();
setupSearch();
setupCopyAccessKeyButtons();
}
function initModals() {
var policyModalEl = document.getElementById('policyEditorModal');
var editModalEl = document.getElementById('editUserModal');
var deleteModalEl = document.getElementById('deleteUserModal');
var rotateModalEl = document.getElementById('rotateSecretModal');
if (policyModalEl) policyModal = new bootstrap.Modal(policyModalEl);
if (editModalEl) editUserModal = new bootstrap.Modal(editModalEl);
if (deleteModalEl) deleteUserModal = new bootstrap.Modal(deleteModalEl);
if (rotateModalEl) rotateSecretModal = new bootstrap.Modal(rotateModalEl);
}
function setupJsonAutoIndent() {
window.UICore.setupJsonAutoIndent(document.getElementById('policyEditorDocument'));
window.UICore.setupJsonAutoIndent(document.getElementById('createUserPolicies'));
}
function setupCopyButtons() {
document.querySelectorAll('.config-copy').forEach(function(button) {
button.addEventListener('click', async function() {
var targetId = button.dataset.copyTarget;
var target = document.getElementById(targetId);
if (!target) return;
await window.UICore.copyToClipboard(target.innerText, button, 'Copy JSON');
});
});
var secretCopyButton = document.querySelector('[data-secret-copy]');
if (secretCopyButton) {
secretCopyButton.addEventListener('click', async function() {
var secretInput = document.getElementById('disclosedSecretValue');
if (!secretInput) return;
await window.UICore.copyToClipboard(secretInput.value, secretCopyButton, 'Copy');
});
}
}
function getUserPolicies(accessKey) {
var user = users.find(function(u) { return u.access_key === accessKey; });
return user ? JSON.stringify(user.policies, null, 2) : '';
}
function applyPolicyTemplate(name, textareaEl) {
if (policyTemplates[name] && textareaEl) {
textareaEl.value = JSON.stringify(policyTemplates[name], null, 2);
}
}
function setupPolicyEditor() {
var userLabelEl = document.getElementById('policyEditorUserLabel');
var userInputEl = document.getElementById('policyEditorUser');
var textareaEl = document.getElementById('policyEditorDocument');
document.querySelectorAll('[data-policy-template]').forEach(function(button) {
button.addEventListener('click', function() {
applyPolicyTemplate(button.dataset.policyTemplate, textareaEl);
});
});
document.querySelectorAll('[data-policy-editor]').forEach(function(button) {
button.addEventListener('click', function() {
var key = button.getAttribute('data-access-key');
if (!key) return;
userLabelEl.textContent = key;
userInputEl.value = key;
textareaEl.value = getUserPolicies(key);
policyModal.show();
});
});
}
function setupCreateUserModal() {
var createUserPoliciesEl = document.getElementById('createUserPolicies');
document.querySelectorAll('[data-create-policy-template]').forEach(function(button) {
button.addEventListener('click', function() {
applyPolicyTemplate(button.dataset.createPolicyTemplate, createUserPoliciesEl);
});
});
}
function setupEditUserModal() {
var editUserForm = document.getElementById('editUserForm');
var editUserDisplayName = document.getElementById('editUserDisplayName');
document.querySelectorAll('[data-edit-user]').forEach(function(btn) {
btn.addEventListener('click', function() {
var key = btn.dataset.editUser;
var name = btn.dataset.displayName;
currentEditKey = key;
editUserDisplayName.value = name;
editUserForm.action = endpoints.updateUser.replace('ACCESS_KEY', key);
editUserModal.show();
});
});
}
function setupDeleteUserModal() {
var deleteUserForm = document.getElementById('deleteUserForm');
var deleteUserLabel = document.getElementById('deleteUserLabel');
var deleteSelfWarning = document.getElementById('deleteSelfWarning');
document.querySelectorAll('[data-delete-user]').forEach(function(btn) {
btn.addEventListener('click', function() {
var key = btn.dataset.deleteUser;
currentDeleteKey = key;
deleteUserLabel.textContent = key;
deleteUserForm.action = endpoints.deleteUser.replace('ACCESS_KEY', key);
if (key === currentUserKey) {
deleteSelfWarning.classList.remove('d-none');
} else {
deleteSelfWarning.classList.add('d-none');
}
deleteUserModal.show();
});
});
}
function setupRotateSecretModal() {
var rotateUserLabel = document.getElementById('rotateUserLabel');
var confirmRotateBtn = document.getElementById('confirmRotateBtn');
var rotateCancelBtn = document.getElementById('rotateCancelBtn');
var rotateDoneBtn = document.getElementById('rotateDoneBtn');
var rotateSecretConfirm = document.getElementById('rotateSecretConfirm');
var rotateSecretResult = document.getElementById('rotateSecretResult');
var newSecretKeyInput = document.getElementById('newSecretKey');
var copyNewSecretBtn = document.getElementById('copyNewSecret');
document.querySelectorAll('[data-rotate-user]').forEach(function(btn) {
btn.addEventListener('click', function() {
currentRotateKey = btn.dataset.rotateUser;
rotateUserLabel.textContent = currentRotateKey;
rotateSecretConfirm.classList.remove('d-none');
rotateSecretResult.classList.add('d-none');
confirmRotateBtn.classList.remove('d-none');
rotateCancelBtn.classList.remove('d-none');
rotateDoneBtn.classList.add('d-none');
rotateSecretModal.show();
});
});
if (confirmRotateBtn) {
confirmRotateBtn.addEventListener('click', async function() {
if (!currentRotateKey) return;
window.UICore.setButtonLoading(confirmRotateBtn, true, 'Rotating...');
try {
var url = endpoints.rotateSecret.replace('ACCESS_KEY', currentRotateKey);
var response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-CSRFToken': csrfToken
}
});
if (!response.ok) {
var data = await response.json();
throw new Error(data.error || 'Failed to rotate secret');
}
var data = await response.json();
newSecretKeyInput.value = data.secret_key;
rotateSecretConfirm.classList.add('d-none');
rotateSecretResult.classList.remove('d-none');
confirmRotateBtn.classList.add('d-none');
rotateCancelBtn.classList.add('d-none');
rotateDoneBtn.classList.remove('d-none');
} catch (err) {
if (window.showToast) {
window.showToast(err.message, 'Error', 'danger');
}
rotateSecretModal.hide();
} finally {
window.UICore.setButtonLoading(confirmRotateBtn, false);
}
});
}
if (copyNewSecretBtn) {
copyNewSecretBtn.addEventListener('click', async function() {
await window.UICore.copyToClipboard(newSecretKeyInput.value, copyNewSecretBtn, 'Copy');
});
}
if (rotateDoneBtn) {
rotateDoneBtn.addEventListener('click', function() {
window.location.reload();
});
}
}
function createUserCardHtml(accessKey, displayName, policies) {
var admin = isAdminUser(policies);
var cardClass = 'card h-100 iam-user-card' + (admin ? ' iam-admin-card' : '');
var roleBadge = admin
? 'Admin'
: 'User';
var policyBadges = '';
if (policies && policies.length > 0) {
policyBadges = policies.map(function(p) {
var bucketLabel = getBucketLabel(p.bucket);
var permLevel = getPermissionLevel(p.actions);
return '' +
'' + window.UICore.escapeHtml(bucketLabel) + ' · ' + window.UICore.escapeHtml(permLevel) + '';
}).join('');
} else {
policyBadges = 'No policies';
}
var esc = window.UICore.escapeHtml;
return '
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' + esc(displayName) + '
' +
roleBadge +
'' +
'
' +
'
' + esc(accessKey) + '' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Bucket Permissions
' +
'
' + policyBadges + '
' +
'
' +
'
';
}
function attachUserCardHandlers(cardElement, accessKey, displayName) {
var editBtn = cardElement.querySelector('[data-edit-user]');
if (editBtn) {
editBtn.addEventListener('click', function() {
currentEditKey = accessKey;
document.getElementById('editUserDisplayName').value = displayName;
document.getElementById('editUserForm').action = endpoints.updateUser.replace('ACCESS_KEY', accessKey);
editUserModal.show();
});
}
var deleteBtn = cardElement.querySelector('[data-delete-user]');
if (deleteBtn) {
deleteBtn.addEventListener('click', function() {
currentDeleteKey = accessKey;
document.getElementById('deleteUserLabel').textContent = accessKey;
document.getElementById('deleteUserForm').action = endpoints.deleteUser.replace('ACCESS_KEY', accessKey);
var deleteSelfWarning = document.getElementById('deleteSelfWarning');
if (accessKey === currentUserKey) {
deleteSelfWarning.classList.remove('d-none');
} else {
deleteSelfWarning.classList.add('d-none');
}
deleteUserModal.show();
});
}
var rotateBtn = cardElement.querySelector('[data-rotate-user]');
if (rotateBtn) {
rotateBtn.addEventListener('click', function() {
currentRotateKey = accessKey;
document.getElementById('rotateUserLabel').textContent = accessKey;
document.getElementById('rotateSecretConfirm').classList.remove('d-none');
document.getElementById('rotateSecretResult').classList.add('d-none');
document.getElementById('confirmRotateBtn').classList.remove('d-none');
document.getElementById('rotateCancelBtn').classList.remove('d-none');
document.getElementById('rotateDoneBtn').classList.add('d-none');
rotateSecretModal.show();
});
}
var policyBtn = cardElement.querySelector('[data-policy-editor]');
if (policyBtn) {
policyBtn.addEventListener('click', function() {
document.getElementById('policyEditorUserLabel').textContent = accessKey;
document.getElementById('policyEditorUser').value = accessKey;
document.getElementById('policyEditorDocument').value = getUserPolicies(accessKey);
policyModal.show();
});
}
var copyBtn = cardElement.querySelector('[data-copy-access-key]');
if (copyBtn) {
copyBtn.addEventListener('click', function() {
copyAccessKey(copyBtn);
});
}
}
function updateUserCount() {
var countEl = document.querySelector('.card-header .text-muted.small');
if (countEl) {
var count = document.querySelectorAll('.iam-user-card').length;
countEl.textContent = count + ' user' + (count !== 1 ? 's' : '') + ' configured';
}
}
function setupFormHandlers() {
var createUserForm = document.querySelector('#createUserModal form');
if (createUserForm) {
createUserForm.addEventListener('submit', function(e) {
e.preventDefault();
window.UICore.submitFormAjax(createUserForm, {
successMessage: 'User created',
onSuccess: function(data) {
var modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));
if (modal) modal.hide();
createUserForm.reset();
var existingAlert = document.querySelector('.alert.alert-info.border-0.shadow-sm');
if (existingAlert) existingAlert.remove();
if (data.secret_key) {
var alertHtml = '' +
'
' +
'
' +
'
' +
'
New user created: ' + window.UICore.escapeHtml(data.access_key) + '
' +
'
This secret is only shown once. Copy it now and store it securely.
' +
'
' +
'
' +
'
' +
'
' +
'Secret key' +
'' +
'' +
'
';
var container = document.querySelector('.page-header');
if (container) {
container.insertAdjacentHTML('afterend', alertHtml);
document.getElementById('copyNewUserSecret').addEventListener('click', async function() {
await window.UICore.copyToClipboard(data.secret_key, this, 'Copy');
});
}
}
var usersGrid = document.querySelector('.row.g-3');
var emptyState = document.querySelector('.empty-state');
if (emptyState) {
var emptyCol = emptyState.closest('.col-12');
if (emptyCol) emptyCol.remove();
if (!usersGrid) {
var cardBody = document.querySelector('.card-body.px-4.pb-4');
if (cardBody) {
cardBody.innerHTML = '';
usersGrid = cardBody.querySelector('.row.g-3');
}
}
}
if (usersGrid) {
var cardHtml = createUserCardHtml(data.access_key, data.display_name, data.policies);
usersGrid.insertAdjacentHTML('beforeend', cardHtml);
var newCard = usersGrid.lastElementChild;
attachUserCardHandlers(newCard, data.access_key, data.display_name);
users.push({
access_key: data.access_key,
display_name: data.display_name,
policies: data.policies || []
});
updateUserCount();
}
}
});
});
}
var policyEditorForm = document.getElementById('policyEditorForm');
if (policyEditorForm) {
policyEditorForm.addEventListener('submit', function(e) {
e.preventDefault();
var userInputEl = document.getElementById('policyEditorUser');
var key = userInputEl.value;
if (!key) return;
var template = policyEditorForm.dataset.actionTemplate;
policyEditorForm.action = template.replace('ACCESS_KEY_PLACEHOLDER', key);
window.UICore.submitFormAjax(policyEditorForm, {
successMessage: 'Policies updated',
onSuccess: function(data) {
policyModal.hide();
var userCard = document.querySelector('[data-access-key="' + key + '"]');
if (userCard) {
var cardEl = userCard.closest('.iam-user-card');
var badgeContainer = cardEl ? cardEl.querySelector('[data-policy-badges]') : null;
if (badgeContainer && data.policies) {
var badges = data.policies.map(function(p) {
var bl = getBucketLabel(p.bucket);
var pl = getPermissionLevel(p.actions);
return '' +
'' + window.UICore.escapeHtml(bl) + ' · ' + window.UICore.escapeHtml(pl) + '';
}).join('');
badgeContainer.innerHTML = badges || 'No policies';
}
if (cardEl) {
var nowAdmin = isAdminUser(data.policies);
cardEl.classList.toggle('iam-admin-card', nowAdmin);
var roleBadgeEl = cardEl.querySelector('[data-role-badge]');
if (roleBadgeEl) {
if (nowAdmin) {
roleBadgeEl.className = 'iam-role-badge iam-role-admin';
roleBadgeEl.textContent = 'Admin';
} else {
roleBadgeEl.className = 'iam-role-badge iam-role-user';
roleBadgeEl.textContent = 'User';
}
}
}
}
var userIndex = users.findIndex(function(u) { return u.access_key === key; });
if (userIndex >= 0 && data.policies) {
users[userIndex].policies = data.policies;
}
}
});
});
}
var editUserForm = document.getElementById('editUserForm');
if (editUserForm) {
editUserForm.addEventListener('submit', function(e) {
e.preventDefault();
var key = currentEditKey;
window.UICore.submitFormAjax(editUserForm, {
successMessage: 'User updated',
onSuccess: function(data) {
editUserModal.hide();
var newName = data.display_name || document.getElementById('editUserDisplayName').value;
var editBtn = document.querySelector('[data-edit-user="' + key + '"]');
if (editBtn) {
editBtn.setAttribute('data-display-name', newName);
var card = editBtn.closest('.iam-user-card');
if (card) {
var nameEl = card.querySelector('h6');
if (nameEl) {
nameEl.textContent = newName;
nameEl.title = newName;
}
var itemWrapper = card.closest('.iam-user-item');
if (itemWrapper) {
itemWrapper.setAttribute('data-display-name', newName.toLowerCase());
}
}
}
var userIndex = users.findIndex(function(u) { return u.access_key === key; });
if (userIndex >= 0) {
users[userIndex].display_name = newName;
}
if (key === currentUserKey) {
document.querySelectorAll('.sidebar-user .user-name').forEach(function(el) {
var truncated = newName.length > 16 ? newName.substring(0, 16) + '...' : newName;
el.textContent = truncated;
el.title = newName;
});
document.querySelectorAll('.sidebar-user[data-username]').forEach(function(el) {
el.setAttribute('data-username', newName);
});
}
}
});
});
}
var deleteUserForm = document.getElementById('deleteUserForm');
if (deleteUserForm) {
deleteUserForm.addEventListener('submit', function(e) {
e.preventDefault();
var key = currentDeleteKey;
window.UICore.submitFormAjax(deleteUserForm, {
successMessage: 'User deleted',
onSuccess: function(data) {
deleteUserModal.hide();
if (key === currentUserKey) {
window.location.href = '/ui/';
return;
}
var deleteBtn = document.querySelector('[data-delete-user="' + key + '"]');
if (deleteBtn) {
var cardCol = deleteBtn.closest('[class*="col-"]');
if (cardCol) {
cardCol.remove();
}
}
users = users.filter(function(u) { return u.access_key !== key; });
updateUserCount();
}
});
});
}
}
function setupSearch() {
var searchInput = document.getElementById('iam-user-search');
if (!searchInput) return;
searchInput.addEventListener('input', function() {
var query = searchInput.value.toLowerCase().trim();
var items = document.querySelectorAll('.iam-user-item');
var noResults = document.getElementById('iam-no-results');
var visibleCount = 0;
items.forEach(function(item) {
var name = item.getAttribute('data-display-name') || '';
var key = item.getAttribute('data-access-key-filter') || '';
var matches = !query || name.indexOf(query) >= 0 || key.indexOf(query) >= 0;
item.classList.toggle('d-none', !matches);
if (matches) visibleCount++;
});
if (noResults) {
noResults.classList.toggle('d-none', visibleCount > 0);
}
});
}
function copyAccessKey(btn) {
var key = btn.getAttribute('data-copy-access-key');
if (!key) return;
var originalHtml = btn.innerHTML;
navigator.clipboard.writeText(key).then(function() {
btn.innerHTML = '';
btn.style.color = '#22c55e';
setTimeout(function() {
btn.innerHTML = originalHtml;
btn.style.color = '';
}, 1200);
}).catch(function() {});
}
function setupCopyAccessKeyButtons() {
document.querySelectorAll('[data-copy-access-key]').forEach(function(btn) {
btn.addEventListener('click', function() {
copyAccessKey(btn);
});
});
}
return {
init: init
};
})();