@ -2095,388 +2095,95 @@ function escapeHtml(text) {
return text . replace ( /[&<>"']/g , function ( m ) { return map [ m ] ; } ) ;
}
// ============================================================================
// USER MANAGEMENT FUNCTIONS
// SHARED MODAL UTILITIES FOR ACCESS KEY MANAGEMENT
// ============================================================================
// Global variables for user management
let currentEditingUser = '' ;
let currentAccessKeysUser = '' ;
// User Management Functions
async function handleCreateUser ( ) {
const form = document . getElementById ( 'createUserForm' ) ;
const formData = new FormData ( form ) ;
// Get selected actions
const actionsSelect = document . getElementById ( 'actions' ) ;
const selectedActions = Array . from ( actionsSelect . selectedOptions ) . map ( option => option . value ) ;
const userData = {
username : formData . get ( 'username' ) ,
email : formData . get ( 'email' ) ,
actions : selectedActions ,
generate_key : formData . get ( 'generateKey' ) === 'on'
} ;
try {
const response = await fetch ( '/api/users' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
body : JSON . stringify ( userData )
} ) ;
if ( response . ok ) {
const result = await response . json ( ) ;
showSuccessMessage ( 'User created successfully' ) ;
// Show the created access key if generated
if ( result . user && result . user . access_key ) {
showNewAccessKeyModal ( result . user ) ;
}
// Close modal and refresh page
const modal = bootstrap . Modal . getInstance ( document . getElementById ( 'createUserModal' ) ) ;
modal . hide ( ) ;
form . reset ( ) ;
setTimeout ( ( ) => window . location . reload ( ) , 1000 ) ;
} else {
const error = await response . json ( ) ;
showErrorMessage ( 'Failed to create user: ' + ( error . error || 'Unknown error' ) ) ;
}
} catch ( error ) {
console . error ( 'Error creating user:' , error ) ;
showErrorMessage ( 'Failed to create user: ' + error . message ) ;
}
// HTML escaping helper to prevent XSS
function escapeHtmlForAttribute ( text ) {
if ( ! text ) return '' ;
const div = document . createElement ( 'div' ) ;
div . textContent = text ;
return div . innerHTML . replace ( /"/g , '"' ) . replace ( /'/g , ''' ) ;
}
async function editUser ( username ) {
currentEditingUser = username ;
try {
const response = await fetch ( ` /api/users/ ${ username } ` ) ;
if ( response . ok ) {
const user = await response . json ( ) ;
// Populate edit form
document . getElementById ( 'editUsername' ) . value = username ;
document . getElementById ( 'editEmail' ) . value = user . email || '' ;
// Set selected actions
const actionsSelect = document . getElementById ( 'editActions' ) ;
Array . from ( actionsSelect . options ) . forEach ( option => {
option . selected = user . actions && user . actions . includes ( option . value ) ;
} ) ;
// Set selected policies
const policiesSelect = document . getElementById ( 'editPolicies' ) ;
if ( policiesSelect ) {
Array . from ( policiesSelect . options ) . forEach ( option => {
option . selected = user . policy_names && user . policy_names . includes ( option . value ) ;
} ) ;
}
// Show modal
const modal = new bootstrap . Modal ( document . getElementById ( 'editUserModal' ) ) ;
modal . show ( ) ;
} else {
showErrorMessage ( 'Failed to load user details' ) ;
}
} catch ( error ) {
console . error ( 'Error loading user:' , error ) ;
showErrorMessage ( 'Failed to load user details' ) ;
}
}
async function handleUpdateUser ( ) {
const form = document . getElementById ( 'editUserForm' ) ;
const formData = new FormData ( form ) ;
// Get selected actions
const actionsSelect = document . getElementById ( 'editActions' ) ;
const selectedActions = Array . from ( actionsSelect . selectedOptions ) . map ( option => option . value ) ;
// Get selected policies
const policiesSelect = document . getElementById ( 'editPolicies' ) ;
const selectedPolicies = policiesSelect ? Array . from ( policiesSelect . selectedOptions ) . map ( option => option . value ) : [ ] ;
function showModal ( title , content ) {
// Create a dynamic modal
const modalId = 'dynamicModal_' + Date . now ( ) ;
const userData = {
email : formData . get ( 'email' ) ,
actions : selectedActions ,
policy_names : selectedPolicies
} ;
// Create modal structure using DOM to prevent XSS in title
const modalDiv = document . createElement ( 'div' ) ;
modalDiv . className = 'modal fade' ;
modalDiv . id = modalId ;
modalDiv . setAttribute ( 'tabindex' , '-1' ) ;
modalDiv . setAttribute ( 'role' , 'dialog' ) ;
try {
const response = await fetch ( ` /api/users/ ${ currentEditingUser } ` , {
method : 'PUT' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
body : JSON . stringify ( userData )
} ) ;
const modalDialog = document . createElement ( 'div' ) ;
modalDialog . className = 'modal-dialog' ;
modalDialog . setAttribute ( 'role' , 'document' ) ;
if ( response . ok ) {
showSuccessMessage ( 'User updated successfully' ) ;
const modalContent = document . createElement ( 'div' ) ;
modalContent . className = 'modal-content' ;
// Close modal and refresh page
const modal = bootstrap . Modal . getInstance ( document . getElementById ( 'editUserModal' ) ) ;
modal . hide ( ) ;
setTimeout ( ( ) => window . location . reload ( ) , 1000 ) ;
} else {
const error = await response . json ( ) ;
showErrorMessage ( 'Failed to update user: ' + ( error . error || 'Unknown error' ) ) ;
}
} catch ( error ) {
console . error ( 'Error updating user:' , error ) ;
showErrorMessage ( 'Failed to update user: ' + error . message ) ;
}
}
// Header
const modalHeader = document . createElement ( 'div' ) ;
modalHeader . className = 'modal-header' ;
function confirmDeleteUser ( username ) {
confirmAction (
` Are you sure you want to delete user " ${ username } "? This action cannot be undone. ` ,
( ) => deleteUserConfirmed ( username )
) ;
}
const modalTitle = document . createElement ( 'h5' ) ;
modalTitle . className = 'modal-title' ;
modalTitle . textContent = title ; // Safe - uses textContent
function deleteUser ( username ) {
confirmDeleteUser ( username ) ;
}
const closeButton = document . createElement ( 'button' ) ;
closeButton . type = 'button' ;
closeButton . className = 'btn-close' ;
closeButton . setAttribute ( 'data-bs-dismiss' , 'modal' ) ;
async function deleteUserConfirmed ( username ) {
try {
const response = await fetch ( ` /api/users/ ${ username } ` , {
method : 'DELETE'
} ) ;
modalHeader . appendChild ( modalTitle ) ;
modalHeader . appendChild ( closeButton ) ;
if ( response . ok ) {
showSuccessMessage ( 'User deleted successfully' ) ;
setTimeout ( ( ) => window . location . reload ( ) , 1000 ) ;
} else {
const error = await response . json ( ) ;
showErrorMessage ( 'Failed to delete user: ' + ( error . error || 'Unknown error' ) ) ;
}
} catch ( error ) {
console . error ( 'Error deleting user:' , error ) ;
showErrorMessage ( 'Failed to delete user: ' + error . message ) ;
}
}
// Body (content may contain HTML, so use innerHTML)
const modalBody = document . createElement ( 'div' ) ;
modalBody . className = 'modal-body' ;
modalBody . innerHTML = content ;
async function showUserDetails ( username ) {
try {
const response = await fetch ( ` /api/users/ ${ username } ` ) ;
if ( response . ok ) {
const user = await response . json ( ) ;
// Footer
const modalFooter = document . createElement ( 'div' ) ;
modalFooter . className = 'modal-footer' ;
const content = createUserDetailsContent ( user ) ;
document . getElementById ( 'userDetailsContent' ) . innerHTML = content ;
const closeFooterButton = document . createElement ( 'button' ) ;
closeFooterButton . type = 'button' ;
closeFooterButton . className = 'btn btn-secondary' ;
closeFooterButton . setAttribute ( 'data-bs-dismiss' , 'modal' ) ;
closeFooterButton . textContent = 'Close' ;
const modal = new bootstrap . Modal ( document . getElementById ( 'userDetailsModal' ) ) ;
modal . show ( ) ;
} else {
showErrorMessage ( 'Failed to load user details' ) ;
}
} catch ( error ) {
console . error ( 'Error loading user details:' , error ) ;
showErrorMessage ( 'Failed to load user details' ) ;
}
}
modalFooter . appendChild ( closeFooterButton ) ;
function createUserDetailsContent ( user ) {
return `
< div class = "row" >
< div class = "col-md-6" >
< h6 class = "text-muted" > Basic Information < / h 6 >
< table class = "table table-sm" >
< tr >
< td > < strong > Username : < / s t r o n g > < / t d >
< td > $ { escapeHtml ( user . username ) } < / t d >
< / t r >
< tr >
< td > < strong > Email : < / s t r o n g > < / t d >
< td > $ { escapeHtml ( user . email || 'Not set' ) } < / t d >
< / t r >
< / t a b l e >
< / d i v >
< div class = "col-md-6" >
< h6 class = "text-muted" > Permissions < / h 6 >
< div class = "mb-3" >
$ { user . actions && user . actions . length > 0 ?
user . actions . map ( action => ` <span class="badge bg-info me-1"> ${ action } </span> ` ) . join ( '' ) :
'<span class="text-muted">No permissions assigned</span>'
}
< / d i v >
< h6 class = "text-muted" > Access Keys < / h 6 >
$ { user . access_keys && user . access_keys . length > 0 ?
createAccessKeysTable ( user . access_keys ) :
'<p class="text-muted">No access keys</p>'
}
< / d i v >
< / d i v >
` ;
}
// Assemble modal
modalContent . appendChild ( modalHeader ) ;
modalContent . appendChild ( modalBody ) ;
modalContent . appendChild ( modalFooter ) ;
modalDialog . appendChild ( modalContent ) ;
modalDiv . appendChild ( modalDialog ) ;
function createAccessKeysTable ( accessKeys ) {
return `
< div class = "table-responsive" >
< table class = "table table-sm" >
< thead >
< tr >
< th > Access Key < / t h >
< th > Created < / t h >
< / t r >
< / t h e a d >
< tbody >
$ { accessKeys . map ( key => `
< tr >
< td > < code > $ { key . access_key } < / c o d e > < / t d >
< td > $ { new Date ( key . created_at ) . toLocaleDateString ( ) } < / t d >
< / t r >
` ).join('')}
< / t b o d y >
< / t a b l e >
< / d i v >
` ;
}
async function manageAccessKeys ( username ) {
currentAccessKeysUser = username ;
document . getElementById ( 'accessKeysUsername' ) . textContent = username ;
await loadAccessKeys ( username ) ;
// Add modal to body
document . body . appendChild ( modalDiv ) ;
const modal = new bootstrap . Modal ( document . getElementById ( 'accessKeysModal' ) ) ;
// Show modal
const modal = new bootstrap . Modal ( document . getElementById ( modalId ) ) ;
modal . show ( ) ;
}
async function loadAccessKeys ( username ) {
try {
const response = await fetch ( ` /api/users/ ${ username } ` ) ;
if ( response . ok ) {
const user = await response . json ( ) ;
const content = createAccessKeysManagementContent ( user . access_keys || [ ] ) ;
document . getElementById ( 'accessKeysContent' ) . innerHTML = content ;
} else {
document . getElementById ( 'accessKeysContent' ) . innerHTML = '<p class="text-muted">Failed to load access keys</p>' ;
}
} catch ( error ) {
console . error ( 'Error loading access keys:' , error ) ;
document . getElementById ( 'accessKeysContent' ) . innerHTML = '<p class="text-muted">Error loading access keys</p>' ;
}
}
function createAccessKeysManagementContent ( accessKeys ) {
if ( accessKeys . length === 0 ) {
return '<p class="text-muted">No access keys found. Create one to get started.</p>' ;
}
return `
< div class = "table-responsive" >
< table class = "table table-hover" >
< thead >
< tr >
< th > Access Key < / t h >
< th > Secret Key < / t h >
< th > Created < / t h >
< th > Actions < / t h >
< / t r >
< / t h e a d >
< tbody >
$ { accessKeys . map ( key => `
< tr >
< td >
< code > $ { key . access_key } < / c o d e >
< button class = "btn btn-sm btn-outline-secondary ms-2" onclick = "adminCopyToClipboard('${key.access_key}')" >
< i class = "fas fa-copy" > < / i >
< / b u t t o n >
< / t d >
< td >
< code class = "text-muted" > • • • • • • • • • • • • • • • • < / c o d e >
< button class = "btn btn-sm btn-outline-secondary ms-2" onclick = "showSecretKey('${key.access_key}', '${key.secret_key}')" >
< i class = "fas fa-eye" > < / i >
< / b u t t o n >
< / t d >
< td > $ { new Date ( key . created_at ) . toLocaleDateString ( ) } < / t d >
< td >
< button class = "btn btn-sm btn-outline-danger" onclick = "confirmDeleteAccessKey('${key.access_key}')" >
< i class = "fas fa-trash" > < / i >
< / b u t t o n >
< / t d >
< / t r >
` ).join('')}
< / t b o d y >
< / t a b l e >
< / d i v >
` ;
}
async function createAccessKey ( ) {
if ( ! currentAccessKeysUser ) {
showErrorMessage ( 'No user selected' ) ;
return ;
}
try {
const response = await fetch ( ` /api/users/ ${ currentAccessKeysUser } /access-keys ` , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
}
} ) ;
if ( response . ok ) {
const result = await response . json ( ) ;
showSuccessMessage ( 'Access key created successfully' ) ;
// Show the new access key
showNewAccessKeyModal ( result . access_key ) ;
// Reload access keys
await loadAccessKeys ( currentAccessKeysUser ) ;
} else {
const error = await response . json ( ) ;
showErrorMessage ( 'Failed to create access key: ' + ( error . error || 'Unknown error' ) ) ;
}
} catch ( error ) {
console . error ( 'Error creating access key:' , error ) ;
showErrorMessage ( 'Failed to create access key: ' + error . message ) ;
}
}
function confirmDeleteAccessKey ( accessKeyId ) {
confirmAction (
` Are you sure you want to delete access key " ${ accessKeyId } "? This action cannot be undone. ` ,
( ) => deleteAccessKeyConfirmed ( accessKeyId )
) ;
}
async function deleteAccessKeyConfirmed ( accessKeyId ) {
try {
const response = await fetch ( ` /api/users/ ${ currentAccessKeysUser } /access-keys/ ${ accessKeyId } ` , {
method : 'DELETE'
} ) ;
if ( response . ok ) {
showSuccessMessage ( 'Access key deleted successfully' ) ;
// Reload access keys
await loadAccessKeys ( currentAccessKeysUser ) ;
} else {
const error = await response . json ( ) ;
showErrorMessage ( 'Failed to delete access key: ' + ( error . error || 'Unknown error' ) ) ;
}
} catch ( error ) {
console . error ( 'Error deleting access key:' , error ) ;
showErrorMessage ( 'Failed to delete access key: ' + error . message ) ;
}
// Remove modal from DOM when hidden
document . getElementById ( modalId ) . addEventListener ( 'hidden.bs.modal' , function ( ) {
this . remove ( ) ;
} ) ;
}
function showSecretKey ( accessKey , secretKey ) {
const modalId = 'secretKeyModal_' + Date . now ( ) ;
const escapedAccessKey = escapeHtmlForAttribute ( accessKey ) ;
const escapedSecretKey = escapeHtmlForAttribute ( secretKey ) ;
const content = `
< div class = "alert alert-info" >
< i class = "fas fa-info-circle me-2" > < / i >
@ -2485,8 +2192,8 @@ function showSecretKey(accessKey, secretKey) {
< div class = "mb-3" >
< label class = "form-label" > < strong > Access Key : < / s t r o n g > < / l a b e l >
< div class = "input-group" >
< input type = "text" class = "form-control" value = "${accessKey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "adminCopyToClipboard('${accessKey} ')" >
< input type = "text" id = "${modalId}_accessKey" class = "form-control" value = "${esc apedA ccessKey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "copyFromInput('${modalId}_accessKey ')" >
< i class = "fas fa-copy" > < / i >
< / b u t t o n >
< / d i v >
@ -2494,8 +2201,8 @@ function showSecretKey(accessKey, secretKey) {
< div class = "mb-3" >
< label class = "form-label" > < strong > Secret Key : < / s t r o n g > < / l a b e l >
< div class = "input-group" >
< input type = "text" class = "form-control" value = "${secretKey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "adminCopyToClipboard('${secretKey} ')" >
< input type = "text" id = "${modalId}_secretKey" class = "form-control" value = "${e scapedS ecretKey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "copyFromInput('${modalId}_secretKey ')" >
< i class = "fas fa-copy" > < / i >
< / b u t t o n >
< / d i v >
@ -2506,20 +2213,20 @@ function showSecretKey(accessKey, secretKey) {
}
function showNewAccessKeyModal ( accessKeyData ) {
const modalId = 'newKeyModal_' + Date . now ( ) ;
const escapedAccessKey = escapeHtmlForAttribute ( accessKeyData . access_key ) ;
const escapedSecretKey = escapeHtmlForAttribute ( accessKeyData . secret_key ) ;
const content = `
< div class = "alert alert-success" >
< i class = "fas fa-check-circle me-2" > < / i >
< strong > Success ! < / s t r o n g > Y o u r n e w a c c e s s k e y h a s b e e n c r e a t e d .
< / d i v >
< div class = "alert alert-info" >
< i class = "fas fa-info-circle me-2" > < / i >
< strong > Important : < / s t r o n g > T h e s e c r e d e n t i a l s p r o v i d e a c c e s s t o y o u r o b j e c t s t o r a g e . K e e p t h e m s e c u r e a n d d o n ' t s h a r e t h e m . Y o u c a n v i e w t h e m a g a i n t h r o u g h t h e u s e r m a n a g e m e n t i n t e r f a c e i f n e e d e d .
< / d i v >
< div class = "mb-3" >
< label class = "form-label" > < strong > Access Key : < / s t r o n g > < / l a b e l >
< div class = "input-group" >
< input type = "text" class = "form-control" value = "${accessKeyData.access_k ey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "adminCopyToClipboard('${accessKeyData.access_key} ')" >
< input type = "text" id = "${modalId}_accessKey" class = "form-control" value = "${escapedAccessKey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "copyFromInput('${modalId}_accessKey ')" >
< i class = "fas fa-copy" > < / i >
< / b u t t o n >
< / d i v >
@ -2527,8 +2234,8 @@ function showNewAccessKeyModal(accessKeyData) {
< div class = "mb-3" >
< label class = "form-label" > < strong > Secret Key : < / s t r o n g > < / l a b e l >
< div class = "input-group" >
< input type = "text" class = "form-control" value = "${accessKeyData.secret_k ey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "adminCopyToClipboard('${accessKeyData.secret_key} ')" >
< input type = "text" id = "${modalId}_secretKey" class = "form-control" value = "${escapedSecretK ey}" readonly >
< button class = "btn btn-outline-secondary" onclick = "copyFromInput('${modalId}_secretKey ')" >
< i class = "fas fa-copy" > < / i >
< / b u t t o n >
< / d i v >
@ -2538,40 +2245,32 @@ function showNewAccessKeyModal(accessKeyData) {
showModal ( 'New Access Key Created' , content ) ;
}
function showModal ( title , content ) {
// Create a dynamic modal
const modalId = 'dynamicModal_' + Date . now ( ) ;
const modalHtml = `
< div class = "modal fade" id = "${modalId}" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" role = "document" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > $ { title } < / h 5 >
< button type = "button" class = "btn-close" data - bs - dismiss = "modal" > < / b u t t o n >
< / d i v >
< div class = "modal-body" >
$ { content }
< / d i v >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data - bs - dismiss = "modal" > Close < / b u t t o n >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
` ;
// Add modal to body
document . body . insertAdjacentHTML ( 'beforeend' , modalHtml ) ;
// Show modal
const modal = new bootstrap . Modal ( document . getElementById ( modalId ) ) ;
modal . show ( ) ;
// Helper function to copy from an input field
function copyFromInput ( inputId ) {
const input = document . getElementById ( inputId ) ;
if ( input ) {
input . select ( ) ;
input . setSelectionRange ( 0 , 99999 ) ; // For mobile devices
// Remove modal from DOM when hidden
document . getElementById ( modalId ) . addEventListener ( 'hidden.bs.modal' , function ( ) {
this . remove ( ) ;
} ) ;
try {
const successful = document . execCommand ( 'copy' ) ;
if ( successful ) {
showAlert ( 'success' , 'Copied to clipboard!' ) ;
} else {
// Try modern clipboard API as fallback
navigator . clipboard . writeText ( input . value ) . then ( ( ) => {
showAlert ( 'success' , 'Copied to clipboard!' ) ;
} ) . catch ( ( ) => {
showAlert ( 'danger' , 'Failed to copy' ) ;
} ) ;
}
} catch ( err ) {
// Try modern clipboard API as fallback
navigator . clipboard . writeText ( input . value ) . then ( ( ) => {
showAlert ( 'success' , 'Copied to clipboard!' ) ;
} ) . catch ( ( ) => {
showAlert ( 'danger' , 'Failed to copy' ) ;
} ) ;
}
}
}