[fix](trx-frontend-http): block Guest password changes
CI / lint (pull_request) Successful in 2m25s
CI / test (pull_request) Successful in 9m5s
CI / frontend (pull_request) Successful in 5m23s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m24s
CI / test (push) Successful in 9m11s
CI / frontend (push) Successful in 5m22s
CI / reuse (push) Successful in 5s
CI / lint (pull_request) Successful in 2m25s
CI / test (pull_request) Successful in 9m5s
CI / frontend (pull_request) Successful in 5m23s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m24s
CI / test (push) Successful in 9m11s
CI / frontend (push) Successful in 5m22s
CI / reuse (push) Successful in 5s
This commit was merged in pull request #65.
This commit is contained in:
@@ -6097,6 +6097,14 @@ async function refreshUserManagement() {
|
|||||||
password.className = "auth-input";
|
password.className = "auth-input";
|
||||||
password.minLength = 8;
|
password.minLength = 8;
|
||||||
password.maxLength = 1024;
|
password.maxLength = 1024;
|
||||||
|
const syncPasswordAccess = () => {
|
||||||
|
const guestActive = guestInput?.checked === true;
|
||||||
|
password.disabled = guestActive;
|
||||||
|
password.title = guestActive ? "Password changes are unavailable while the Guest role is active" : "";
|
||||||
|
if (guestActive) password.value = "";
|
||||||
|
};
|
||||||
|
roleInputs.forEach(({ input }) => input.addEventListener("change", syncPasswordAccess));
|
||||||
|
syncPasswordAccess();
|
||||||
const save = document.createElement("button");
|
const save = document.createElement("button");
|
||||||
save.type = "button";
|
save.type = "button";
|
||||||
save.textContent = "Save";
|
save.textContent = "Save";
|
||||||
|
|||||||
@@ -5173,6 +5173,14 @@ async function refreshUserManagement() {
|
|||||||
}
|
}
|
||||||
const password = document.createElement("input");
|
const password = document.createElement("input");
|
||||||
password.type = "password"; password.placeholder = "New password (8+ characters)"; password.autocomplete = "new-password"; password.className = "auth-input"; password.minLength = 8; password.maxLength = 1024;
|
password.type = "password"; password.placeholder = "New password (8+ characters)"; password.autocomplete = "new-password"; password.className = "auth-input"; password.minLength = 8; password.maxLength = 1024;
|
||||||
|
const syncPasswordAccess = () => {
|
||||||
|
const guestActive = guestInput?.checked === true;
|
||||||
|
password.disabled = guestActive;
|
||||||
|
password.title = guestActive ? "Password changes are unavailable while the Guest role is active" : "";
|
||||||
|
if (guestActive) password.value = "";
|
||||||
|
};
|
||||||
|
roleInputs.forEach(({ input }) => input.addEventListener("change", syncPasswordAccess));
|
||||||
|
syncPasswordAccess();
|
||||||
const save = document.createElement("button"); save.type = "button"; save.textContent = "Save";
|
const save = document.createElement("button"); save.type = "button"; save.textContent = "Save";
|
||||||
save.addEventListener("click", async () => {
|
save.addEventListener("click", async () => {
|
||||||
const changes: { roles?: AuthRole[]; password?: string; enabled?: boolean } = {
|
const changes: { roles?: AuthRole[]; password?: string; enabled?: boolean } = {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const fixture = await startWebFixture({
|
|||||||
},
|
},
|
||||||
users: [
|
users: [
|
||||||
{ username: "admin", roles: ALL_ROLES, enabled: true },
|
{ username: "admin", roles: ALL_ROLES, enabled: true },
|
||||||
|
{ username: "guest", roles: ["guest"], enabled: true },
|
||||||
{ username: "listener", roles: ["read"], enabled: false },
|
{ username: "listener", roles: ["read"], enabled: false },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -41,6 +42,7 @@ try {
|
|||||||
const rows = [...document.querySelectorAll("#user-list > .sch-row")];
|
const rows = [...document.querySelectorAll("#user-list > .sch-row")];
|
||||||
const rowFor = (username) => rows.find((row) => row.querySelector("strong")?.textContent.startsWith(username));
|
const rowFor = (username) => rows.find((row) => row.querySelector("strong")?.textContent.startsWith(username));
|
||||||
const admin = rowFor("admin");
|
const admin = rowFor("admin");
|
||||||
|
const guest = rowFor("guest");
|
||||||
const listener = rowFor("listener");
|
const listener = rowFor("listener");
|
||||||
const role = (row, value) => row?.querySelector(`input[value="${value}"]`);
|
const role = (row, value) => row?.querySelector(`input[value="${value}"]`);
|
||||||
return {
|
return {
|
||||||
@@ -49,6 +51,7 @@ try {
|
|||||||
adminRoleLocked: role(admin, "administrator")?.disabled,
|
adminRoleLocked: role(admin, "administrator")?.disabled,
|
||||||
adminGuestLocked: role(admin, "guest")?.disabled,
|
adminGuestLocked: role(admin, "guest")?.disabled,
|
||||||
adminRemoveLocked: admin?.querySelector("button.danger")?.disabled,
|
adminRemoveLocked: admin?.querySelector("button.danger")?.disabled,
|
||||||
|
guestPasswordLocked: guest?.querySelector('input[type="password"]')?.disabled,
|
||||||
listenerEnabled: listener?.querySelector('input[type="checkbox"]')?.checked,
|
listenerEnabled: listener?.querySelector('input[type="checkbox"]')?.checked,
|
||||||
listenerRead: role(listener, "read")?.checked,
|
listenerRead: role(listener, "read")?.checked,
|
||||||
};
|
};
|
||||||
@@ -58,8 +61,16 @@ try {
|
|||||||
assert.equal(state.adminRoleLocked, true);
|
assert.equal(state.adminRoleLocked, true);
|
||||||
assert.equal(state.adminGuestLocked, true);
|
assert.equal(state.adminGuestLocked, true);
|
||||||
assert.equal(state.adminRemoveLocked, true);
|
assert.equal(state.adminRemoveLocked, true);
|
||||||
|
assert.equal(state.guestPasswordLocked, true);
|
||||||
assert.equal(state.listenerEnabled, false);
|
assert.equal(state.listenerEnabled, false);
|
||||||
assert.equal(state.listenerRead, true);
|
assert.equal(state.listenerRead, true);
|
||||||
|
|
||||||
|
const listenerRow = page.locator("#user-list > .sch-row").filter({ hasText: "listener" });
|
||||||
|
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 listenerPassword.isDisabled(), false);
|
||||||
assert.deepEqual(runtimeErrors, []);
|
assert.deepEqual(runtimeErrors, []);
|
||||||
} finally {
|
} finally {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|||||||
@@ -514,6 +514,10 @@ impl UserStore {
|
|||||||
roles: Option<AuthRoles>,
|
roles: Option<AuthRoles>,
|
||||||
enabled: Option<bool>,
|
enabled: Option<bool>,
|
||||||
) -> Result<(), UserStoreError> {
|
) -> Result<(), UserStoreError> {
|
||||||
|
let roles = roles
|
||||||
|
.map(validate_roles)
|
||||||
|
.transpose()
|
||||||
|
.map_err(UserStoreError::Invalid)?;
|
||||||
if let Some(value) = password {
|
if let Some(value) = password {
|
||||||
validate_password(value).map_err(UserStoreError::Invalid)?;
|
validate_password(value).map_err(UserStoreError::Invalid)?;
|
||||||
}
|
}
|
||||||
@@ -526,6 +530,16 @@ impl UserStore {
|
|||||||
.get(username)
|
.get(username)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(UserStoreError::NotFound)?;
|
.ok_or(UserStoreError::NotFound)?;
|
||||||
|
if password.is_some()
|
||||||
|
&& roles
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&old.roles)
|
||||||
|
.contains(&AuthRole::Guest)
|
||||||
|
{
|
||||||
|
return Err(UserStoreError::Invalid(
|
||||||
|
"password changes are not available while the guest role is active".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let remains_enabled_administrator = enabled.unwrap_or(old.enabled)
|
let remains_enabled_administrator = enabled.unwrap_or(old.enabled)
|
||||||
&& roles
|
&& roles
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -544,7 +558,7 @@ impl UserStore {
|
|||||||
record.password_hash = hash;
|
record.password_hash = hash;
|
||||||
}
|
}
|
||||||
if let Some(value) = roles {
|
if let Some(value) = roles {
|
||||||
record.roles = validate_roles(value).map_err(UserStoreError::Invalid)?;
|
record.roles = value;
|
||||||
}
|
}
|
||||||
if let Some(value) = enabled {
|
if let Some(value) = enabled {
|
||||||
record.enabled = value;
|
record.enabled = value;
|
||||||
@@ -1666,6 +1680,16 @@ mod tests {
|
|||||||
assert_eq!(response.status(), actix_web::http::StatusCode::OK);
|
assert_eq!(response.status(), actix_web::http::StatusCode::OK);
|
||||||
let admin_cookie = response.response().cookies().next().unwrap().to_string();
|
let admin_cookie = response.response().cookies().next().unwrap().to_string();
|
||||||
|
|
||||||
|
let request = aw_test::TestRequest::patch()
|
||||||
|
.uri("/auth/users/guest")
|
||||||
|
.insert_header((actix_web::http::header::COOKIE, admin_cookie.clone()))
|
||||||
|
.set_json(serde_json::json!({"password":"changed-guest-password"}))
|
||||||
|
.to_request();
|
||||||
|
assert_eq!(
|
||||||
|
aw_test::call_service(&app, request).await.status(),
|
||||||
|
actix_web::http::StatusCode::BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
let request = aw_test::TestRequest::post()
|
let request = aw_test::TestRequest::post()
|
||||||
.uri("/auth/users")
|
.uri("/auth/users")
|
||||||
.insert_header((actix_web::http::header::COOKIE, admin_cookie.clone()))
|
.insert_header((actix_web::http::header::COOKIE, admin_cookie.clone()))
|
||||||
|
|||||||
Reference in New Issue
Block a user