From 1829e3d17b9b7d498d204a1902ae730ea27d2474 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Thu, 13 Aug 2026 02:16:59 +0200 Subject: [PATCH] [fix](trx-frontend-http): make Guest role composable --- .../assets/web/generated/app.js | 7 ++--- .../trx-frontend-http/frontend/src/app.ts | 5 ++-- .../frontend/tests/account-management.mjs | 6 +++- .../trx-frontend-http/src/auth.rs | 28 +++++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js index 281db0be..a6d143b5 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js @@ -1852,10 +1852,9 @@ function buildRoleChoices(selected) { input.addEventListener("change", () => { if (!input.checked) return; if (value === "guest") { - inputs.forEach((choice) => { - if (choice.value !== "guest") choice.input.checked = false; - }); - } else { + const administrator = inputs.find((choice) => choice.value === "administrator"); + if (administrator) administrator.input.checked = false; + } else if (value === "administrator") { const guest = inputs.find((choice) => choice.value === "guest"); if (guest) guest.input.checked = false; } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts index 3cee2585..768f2473 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts @@ -447,8 +447,9 @@ function buildRoleChoices(selected: readonly AuthRole[]) { input.addEventListener("change", () => { if (!input.checked) return; if (value === "guest") { - inputs.forEach(choice => { if (choice.value !== "guest") choice.input.checked = false; }); - } else { + const administrator = inputs.find(choice => choice.value === "administrator"); + if (administrator) administrator.input.checked = false; + } else if (value === "administrator") { const guest = inputs.find(choice => choice.value === "guest"); if (guest) guest.input.checked = false; } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/account-management.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/account-management.mjs index 1ae062ca..f8ffa53a 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/account-management.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/account-management.mjs @@ -69,7 +69,11 @@ try { const listenerPassword = listenerRow.locator('input[type="password"]'); await listenerRow.locator('input[value="guest"]').check(); assert.equal(await listenerPassword.isDisabled(), true); - await listenerRow.locator('input[value="read"]').check(); + assert.equal(await listenerRow.locator('input[value="read"]').isChecked(), true); + await listenerRow.locator('input[value="control"]').check(); + assert.equal(await listenerRow.locator('input[value="guest"]').isChecked(), true); + assert.equal(await listenerPassword.isDisabled(), true); + await listenerRow.locator('input[value="guest"]').uncheck(); assert.equal(await listenerPassword.isDisabled(), false); assert.deepEqual(runtimeErrors, []); } finally { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/auth.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/auth.rs index 417d599f..3aea5b7e 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/auth.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/auth.rs @@ -646,8 +646,8 @@ fn validate_roles(roles: AuthRoles) -> Result { if roles.is_empty() { return Err("at least one role is required".to_string()); } - if roles.contains(&AuthRole::Guest) && roles.len() != 1 { - return Err("guest cannot be combined with other roles".to_string()); + if roles.contains(&AuthRole::Guest) && roles.contains(&AuthRole::Administrator) { + return Err("guest cannot be combined with administrator".to_string()); } Ok(roles) } @@ -1428,6 +1428,7 @@ mod tests { #[test] fn test_route_access_allows() { let guest = roles(&[AuthRole::Guest]); + let guest_control = roles(&[AuthRole::Guest, AuthRole::Control]); let read = roles(&[AuthRole::Read]); let control = roles(&[AuthRole::Control]); let transmit = roles(&[AuthRole::Transmit]); @@ -1438,10 +1439,12 @@ mod tests { assert!(RouteAccess::Public.allows(Some(&administrator))); assert!(!RouteAccess::Account.allows(None)); assert!(!RouteAccess::Account.allows(Some(&guest))); + assert!(!RouteAccess::Account.allows(Some(&guest_control))); assert!(RouteAccess::Account.allows(Some(&write))); assert!(!RouteAccess::Read.allows(None)); assert!(RouteAccess::Read.allows(Some(&guest))); + assert!(RouteAccess::Control.allows(Some(&guest_control))); assert!(RouteAccess::Read.allows(Some(&read))); assert!(RouteAccess::Read.allows(Some(&control))); assert!(RouteAccess::Read.allows(Some(&transmit))); @@ -1604,12 +1607,25 @@ mod tests { } #[test] - fn guest_is_an_exclusive_role() { + fn guest_can_be_combined_with_non_administrator_roles() { assert_eq!( - validate_roles(roles(&[AuthRole::Guest])).unwrap(), - roles(&[AuthRole::Guest]) + validate_roles(roles(&[ + AuthRole::Guest, + AuthRole::Read, + AuthRole::Control, + AuthRole::Transmit, + AuthRole::Write, + ])) + .unwrap(), + roles(&[ + AuthRole::Guest, + AuthRole::Read, + AuthRole::Control, + AuthRole::Transmit, + AuthRole::Write, + ]) ); - assert!(validate_roles(roles(&[AuthRole::Guest, AuthRole::Read])).is_err()); + assert!(validate_roles(roles(&[AuthRole::Guest, AuthRole::Administrator])).is_err()); } #[test]