Files
railsgoat/app/views/layouts/shared/_header.html.erb
T
Ken Johnson c5cd2828a5 Fix Bootstrap 5 modal aria-hidden focus timing issue
Added event listeners to manage aria-hidden attribute timing during
modal open/close transitions to prevent accessibility warnings.

Changes:
- Listen to hide.bs.modal to remove aria-hidden before closing
- Listen to hidden.bs.modal to restore aria-hidden after fully closed
- Listen to show.bs.modal to remove aria-hidden when opening
- Use setTimeout to ensure focus has moved before setting aria-hidden

This prevents the "Blocked aria-hidden on element with focus" warning
by ensuring aria-hidden is only set after focus has left the modal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 01:33:45 -05:00

185 lines
7.1 KiB
Plaintext
Executable File

<% if current_user %>
<!-- Authenticated Header -->
<header class="rg-header">
<div class="container-fluid h-100">
<div class="row h-100 align-items-center">
<div class="col-auto">
<a href="<%= home_dashboard_index_path %>" class="rg-brand">
<i class="bi bi-shield-fill-exclamation"></i> RailsGoat
</a>
</div>
<div class="col"></div>
<div class="col-auto">
<div class="d-flex align-items-center gap-3">
<!-- Font Size Controls -->
<div class="btn-group btn-group-sm" role="group" aria-label="Font size controls">
<a href="/dashboard/home?font=8pt" class="btn btn-outline-secondary" style="font-size: 10pt;" title="Small font" aria-label="Small font">
<i class="bi bi-type"></i>
</a>
<a href="/dashboard/home?font=200%25" class="btn btn-outline-secondary" style="font-size: 14pt;" title="Large font" aria-label="Large font">
<i class="bi bi-type"></i>
</a>
</div>
<!-- Tutorial Link -->
<%= button_to "https://github.com/OWASP/railsgoat/wiki", {
method: "get",
class: "btn btn-sm btn-outline-primary",
onclick: "window.open('https://github.com/OWASP/railsgoat/wiki', '_blank'); return false;"
} do %>
<i class="bi bi-book"></i> Tutorials
<% end %>
<!-- User Dropdown -->
<div class="dropdown">
<button class="btn btn-link text-decoration-none dropdown-toggle d-flex align-items-center gap-2" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<div class="bg-primary rounded-circle d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;">
<i class="bi bi-person-fill text-white"></i>
</div>
<!--
VULNERABILITY: XSS via html_safe
I'm going to use HTML safe because we had some weird stuff
going on with funny chars and jquery, plus it says safe so I'm guessing
nothing bad will happen
-->
<span class="text-dark"><%= current_user.first_name.html_safe %></span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<%= link_to user_account_settings_path(user_id: current_user.id), class: "dropdown-item" do %>
<i class="bi bi-gear"></i> Account Settings
<% end %>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<%= link_to logout_path, class: "dropdown-item text-danger" do %>
<i class="bi bi-box-arrow-right"></i> Logout
<% end %>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</header>
<% else %>
<!-- Unauthenticated Header -->
<header class="rg-header">
<div class="container-fluid h-100">
<div class="row h-100 align-items-center">
<div class="col-auto">
<span class="rg-brand">
<i class="bi bi-shield-fill-exclamation"></i> RailsGoat
</span>
</div>
<div class="col"></div>
<div class="col-auto">
<div class="d-flex align-items-center gap-2">
<button type="button" id="show_creds_btn" class="btn btn-sm btn-warning">
<i class="bi bi-key"></i> Demo Credentials
</button>
<%= button_to "https://github.com/OWASP/railsgoat/wiki", {
method: "get",
class: "btn btn-sm btn-outline-primary",
onclick: "window.open('https://github.com/OWASP/railsgoat/wiki', '_blank'); return false;"
} do %>
<i class="bi bi-book"></i> Tutorials
<% end %>
<%= button_to signup_path, {
class: "btn btn-sm btn-primary",
method: "get"
} do %>
<i class="bi bi-person-plus"></i> Sign Up
<% end %>
<%= button_to login_path, {
class: "btn btn-sm btn-outline-primary",
method: "get"
} do %>
<i class="bi bi-box-arrow-in-right"></i> Login
<% end %>
</div>
</div>
</div>
</div>
</header>
<!-- Credentials Modal -->
<div class="modal fade" id="credentialsModal" tabindex="-1" aria-labelledby="credentialsModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="credentialsModalLabel">
<i class="bi bi-key"></i> Demo Credentials
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="modal_content" class="modal-body">
<!-- Content loaded via AJAX -->
</div>
</div>
</div>
</div>
<script>
function initCredentialsModal() {
const showCredsBtn = document.getElementById('show_creds_btn');
const modalElement = document.getElementById('credentialsModal');
if (showCredsBtn && modalElement) {
// Remove any existing listeners
const newBtn = showCredsBtn.cloneNode(true);
showCredsBtn.parentNode.replaceChild(newBtn, showCredsBtn);
// Fix aria-hidden timing issue with Bootstrap 5
modalElement.addEventListener('hide.bs.modal', function() {
// Remove aria-hidden before the modal closes to prevent focus conflict
this.removeAttribute('aria-hidden');
});
modalElement.addEventListener('hidden.bs.modal', function() {
// Add aria-hidden back after modal is fully closed and focus has moved
setTimeout(() => {
this.setAttribute('aria-hidden', 'true');
}, 0);
});
modalElement.addEventListener('show.bs.modal', function() {
// Ensure aria-hidden is removed when opening
this.removeAttribute('aria-hidden');
});
newBtn.addEventListener('click', function(event) {
event.preventDefault();
fetch('<%= credentials_tutorials_path %>')
.then(response => response.text())
.then(html => {
document.getElementById('modal_content').innerHTML = html;
const modal = new bootstrap.Modal(modalElement);
modal.show();
})
.catch(error => {
console.error('Error loading credentials:', error);
document.getElementById('modal_content').innerHTML = '<p class="text-danger">Error loading credentials. Please try again.</p>';
const modal = new bootstrap.Modal(modalElement);
modal.show();
});
});
}
}
// Handle both initial load and Turbolinks navigation
document.addEventListener('DOMContentLoaded', initCredentialsModal);
document.addEventListener('turbolinks:load', initCredentialsModal);
</script>
<% end %>