fix: hide webui user management when server in single-user mode

Single-root deployments reject /list-users with XAdminMethodNotSupported.
Treat that response as a capability signal so the dashboard suppresses
the expected error and removes unsupported user-management controls.
This commit is contained in:
Ben McClelland
2026-09-20 13:07:11 -07:00
parent d03ac3c299
commit 3db22f3d38
3 changed files with 52 additions and 5 deletions
+9
View File
@@ -419,10 +419,19 @@ under the License.
}
async function loadGatewayUsers() {
if (!api.canManageGatewayUsers()) return true;
let users;
try {
users = await api.listUsers();
} catch (error) {
if (error.code === 'XAdminMethodNotSupported') {
api.setCanManageGatewayUsers(false);
document.querySelectorAll('[data-admin-users-only]').forEach(item => {
item.style.display = 'none';
});
return true;
}
console.error('Error loading users:', error);
showToast('Error loading users: ' + error.message, 'error');
return false;
+35 -1
View File
@@ -124,6 +124,7 @@ class VersityAPI {
this._hasIAM = false; // Credentials validate against the IAM service
this._hasS3 = false; // Credentials validate against the S3 data plane
this._canListBuckets = false; // hasS3 is true and s3:ListAllMyBuckets is allowed
this._canManageGatewayUsers = true; // False when the gateway runs in single-user mode
}
/**
@@ -295,6 +296,21 @@ class VersityAPI {
sessionStorage.setItem('vgw_can_list_buckets', this._canListBuckets ? 'true' : 'false');
}
/**
* Check whether this gateway supports its account-management Admin API.
*/
canManageGatewayUsers() {
return this._canManageGatewayUsers;
}
/**
* Record whether the gateway supports its account-management Admin API.
*/
setCanManageGatewayUsers(canManageGatewayUsers) {
this._canManageGatewayUsers = !!canManageGatewayUsers;
sessionStorage.setItem('vgw_can_manage_gateway_users', this._canManageGatewayUsers ? 'true' : 'false');
}
/**
* Load credentials from sessionStorage
*/
@@ -310,6 +326,7 @@ class VersityAPI {
const hasIAM = sessionStorage.getItem('vgw_has_iam') === 'true';
const hasS3 = sessionStorage.getItem('vgw_has_s3') === 'true';
const canListBuckets = sessionStorage.getItem('vgw_can_list_buckets') === 'true';
const canManageGatewayUsers = sessionStorage.getItem('vgw_can_manage_gateway_users') !== 'false';
// Support legacy single endpoint storage
const legacyEndpoint = sessionStorage.getItem('vgw_endpoint');
@@ -325,6 +342,7 @@ class VersityAPI {
this._hasIAM = hasIAM;
this._hasS3 = hasS3;
this._canListBuckets = canListBuckets;
this._canManageGatewayUsers = canManageGatewayUsers;
return true;
}
return false;
@@ -343,6 +361,7 @@ class VersityAPI {
this._hasIAM = false;
this._hasS3 = false;
this._canListBuckets = false;
this._canManageGatewayUsers = true;
this._userType = 'user';
this._accessibleGateways = [];
sessionStorage.removeItem('vgw_admin_endpoint');
@@ -357,6 +376,7 @@ class VersityAPI {
sessionStorage.removeItem('vgw_has_iam');
sessionStorage.removeItem('vgw_has_s3');
sessionStorage.removeItem('vgw_can_list_buckets');
sessionStorage.removeItem('vgw_can_manage_gateway_users');
sessionStorage.removeItem('vgw_iam_probe_error');
sessionStorage.removeItem('vgw_user_type');
sessionStorage.removeItem('vgw_accessible_gateways');
@@ -857,7 +877,9 @@ class VersityAPI {
const responseText = await response.text();
if (!response.ok) {
throw new Error(parseXmlErrorMessage(responseText, `HTTP ${response.status}: ${response.statusText}`));
const error = new Error(parseXmlErrorMessage(responseText, `HTTP ${response.status}: ${response.statusText}`));
error.code = parseXmlErrorCode(responseText);
throw error;
}
return responseText;
@@ -2851,6 +2873,18 @@ function parseXmlErrorMessage(responseText, fallback) {
return fallback;
}
/**
* Extract an S3-compatible error code from an XML response body.
*/
function parseXmlErrorCode(responseText) {
try {
const xmlDoc = new DOMParser().parseFromString(responseText, 'text/xml');
return xmlDoc.querySelector('Code')?.textContent || null;
} catch (e) {
return null;
}
}
/**
* Normalize a parsed member-list field to an array
*/
+8 -4
View File
@@ -101,6 +101,10 @@ function requireGatewayUsers() {
window.location.href = 'iam-users.html';
return false;
}
if (!api.canManageGatewayUsers()) {
window.location.href = defaultLandingPage();
return false;
}
return requireAdmin();
}
@@ -420,9 +424,9 @@ function updateUserInfo() {
* standalone-IAM deployment
* data-s3-only the S3 data plane answered for these credentials
* data-iam-only the standalone IAM service answered for them
* data-admin-users-only the inverse of data-iam-only: the gateway's own
* account store is still the user directory, rather
* than a configured standalone IAM service
* data-admin-users-only the gateway's own account store is available for
* management, rather than standalone IAM or
* single-user mode
*/
function initSidebarWithRole() {
initSidebar();
@@ -435,7 +439,7 @@ function initSidebarWithRole() {
if (!api.hasManagement()) hide('[data-management-only]');
if (!api.hasS3()) hide('[data-s3-only]');
if (!api.hasIAM()) hide('[data-iam-only]');
if (api.hasIAM()) hide('[data-admin-users-only]');
if (api.hasIAM() || !api.canManageGatewayUsers()) hide('[data-admin-users-only]');
reportIAMProbeFailure();
}