Files
sso-manager-node/nodejs/views/impersonate_modal.ejs
wmantly 3c12ebba16 Remove all native alert()/confirm() calls
Native confirm() dialogs block browser automation entirely (discovered
via a frozen tab while browser-testing the app.messages/app.modal
adoption), and native alert()/confirm() are visually inconsistent with
the rest of the UI. Replaced every call site with
app.messages.action/confirm/toast:

- directory.ejs: rotateSecret/deleteResource confirms and all inline
  save/add/remove-group/edge error alerts now target #resourceModal's
  actionMessage (or, for deleteResource — called from the outer table
  row, not the modal — the page's own card).
- impersonate_modal.ejs, onboarding.ejs: no local .actionMessage target
  exists on these pages, so their alerts became page-wide toasts.
- executive.ejs: two alerts in sendNotification's validation now use the
  existing $compose target; saveTos's alert now reuses the function's
  own msgEl inline-message element instead of introducing a second
  mechanism.
- users.ejs, profile.ejs, proxy's profile.ejs: toggleActive's alert
  (no row context available at the call site) became a toast;
  revokeInvite/revokeToken/rotateToken use the row/card element already
  in scope.
- app.js: removed app.user.remove and app.oauthClient.remove, which
  contained native confirm() guards and had zero callers anywhere in the
  app — dead code, deleted rather than converted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 16:50:07 -04:00

90 lines
3.4 KiB
Plaintext

<!-- Impersonation credentials modal — include in any page that has an impersonate button -->
<div class="modal fade" id="impersonateModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title">
<i class="fa-solid fa-user-secret"></i>
Impersonating <span id="impersonateModalTitle" class="fw-bold"></span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning d-flex align-items-center mb-3" role="alert">
<i class="fa-solid fa-triangle-exclamation me-2"></i>
<div>The real password is <strong>unchanged</strong>. This temporary credential expires automatically.</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Username</label>
<input type="text" class="form-control font-monospace" id="impersonateModalUid" readonly>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Temporary Password</label>
<div class="input-group">
<input type="text" class="form-control font-monospace" id="impersonateModalPassword" readonly>
<button class="btn btn-outline-secondary" id="impersonateCopyBtn" type="button" title="Copy password">
<i class="fa-solid fa-copy"></i>
</button>
</div>
</div>
<p class="text-muted small mb-0">
<i class="fa-solid fa-clock"></i>
Expires at <strong id="impersonateModalExpires"></strong> &mdash; works for any LDAP-backed service.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="impersonateStopBtn">
<i class="fa-solid fa-circle-stop"></i> Stop Impersonating
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
(function(){
// Avoid re-binding if the modal JS was already loaded (e.g. two includes on same page)
if(window._impersonateModalBound) return;
window._impersonateModalBound = true;
$(document).ready(function(){
$('#impersonateCopyBtn').on('click', function(){
var pw = $('#impersonateModalPassword').val();
navigator.clipboard.writeText(pw).then(function(){
$('#impersonateCopyBtn')
.html('<i class="fa-solid fa-check"></i>')
.addClass('btn-success').removeClass('btn-outline-secondary');
setTimeout(function(){
$('#impersonateCopyBtn')
.html('<i class="fa-solid fa-copy"></i>')
.addClass('btn-outline-secondary').removeClass('btn-success');
}, 1500);
});
});
});
})();
function startImpersonate(uid){
app.impersonate.create(uid, function(error, data){
if(error){
app.messages.toast('Could not start impersonation: ' + (data && data.message ? data.message : 'Unknown error'), 'danger');
return;
}
$('#impersonateModalTitle').text(data.uid);
$('#impersonateModalUid').val(data.uid);
$('#impersonateModalPassword').val(data.temp_password);
$('#impersonateModalExpires').text(new Date(data.expires_at).toLocaleTimeString());
$('#impersonateStopBtn').off('click').on('click', function(){
app.impersonate.revoke(data.uid, function(err){
$('#impersonateModal').modal('hide');
if(!err) app.messages.action('Impersonation ended for ' + data.uid, $('body'), 'success');
});
});
$('#impersonateModal').modal('show');
});
}
</script>