panama-sudo is pkexec with a stated reason: the reason travels to the shell over the existing polkit IPC target, and the prompt renders it labeled "Stated reason (unverified)" beside polkitd's real action message -- beside, never instead of, because any process can claim any reason and the action text is the trust anchor. Reasons are single-shot and expire in ten seconds, so a stale one cannot dress up an unrelated prompt; without a reason, a running shell, or qs the wrapper is exactly pkexec. Built for agents, so the person typing their password learns what for. Verified live end to end -- reason shown, consumed once, expired when stale, cleared on dismissal -- and pinned by the polkit reason contract.
186 lines
5.8 KiB
QML
186 lines
5.8 KiB
QML
// The authentication prompt.
|
|
//
|
|
// Deliberately a Panama surface rather than the compositor's stock agent: this
|
|
// is the window that asks for the password to everything, and it was the one
|
|
// window on the desktop that looked like it belonged to something else.
|
|
//
|
|
// Two things here are security, not styling. It takes EXCLUSIVE keyboard focus,
|
|
// so keystrokes cannot reach the window underneath while a password is being
|
|
// typed. And the field is cleared on every exit path, including the ones nobody
|
|
// plans for.
|
|
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
import qs.modules.settings
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
visible: Polkit.active
|
|
color: "transparent"
|
|
|
|
anchors { top: true; bottom: true; left: true; right: true }
|
|
exclusiveZone: 0
|
|
|
|
WlrLayershell.namespace: "qs-polkit"
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
// Exclusive, not OnDemand: a password prompt that lets keystrokes through
|
|
// to whatever is behind it is a keylogger with extra steps.
|
|
WlrLayershell.keyboardFocus: Polkit.active
|
|
? WlrKeyboardFocus.Exclusive
|
|
: WlrKeyboardFocus.None
|
|
|
|
onVisibleChanged: {
|
|
if (root.visible) {
|
|
field.text = "";
|
|
field.forceActiveFocus();
|
|
} else {
|
|
field.text = "";
|
|
}
|
|
}
|
|
|
|
// Dims what is behind, and swallows clicks so nothing outside the dialog
|
|
// can be operated while it is waiting.
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: Theme.alpha(Theme.bgDark, Theme.overlayAlpha)
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
// Clicking outside does nothing on purpose. Dismissing an
|
|
// authentication request by misclick, and having the thing that
|
|
// asked report a mysterious failure, is worse than an explicit
|
|
// Cancel.
|
|
hoverEnabled: true
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: dialog
|
|
|
|
anchors.centerIn: parent
|
|
width: 420
|
|
implicitHeight: layout.implicitHeight + 44
|
|
radius: Theme.popoverRadius
|
|
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
|
|
border.width: 1
|
|
border.color: Theme.alpha(Theme.fg, 0.1)
|
|
|
|
Column {
|
|
id: layout
|
|
anchors.centerIn: parent
|
|
width: parent.width - 44
|
|
spacing: 14
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: "Authentication required"
|
|
color: Theme.fg
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.DemiBold
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: Polkit.message
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
// The caller's stated reason, when panama-sudo passed one.
|
|
// Untrusted commentary from an unprivileged process, so it is
|
|
// labeled as a claim and drawn beside polkitd's message above --
|
|
// never in place of it. The real action text is the trust anchor.
|
|
Column {
|
|
width: parent.width
|
|
visible: Polkit.statedReason !== ""
|
|
spacing: 2
|
|
|
|
Text {
|
|
text: "Stated reason (unverified)"
|
|
color: Theme.fgMuted
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.DemiBold
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: Polkit.statedReason
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
font.italic: true
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
visible: Polkit.users.length > 1
|
|
text: "Authenticating as " + Polkit.chosenUser
|
|
color: Theme.fgMuted
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
|
|
PasswordField {
|
|
id: field
|
|
width: parent.width
|
|
placeholder: "Password"
|
|
enabled: !Polkit.authenticating
|
|
onAccepted: {
|
|
if (field.text !== "")
|
|
Polkit.submit(field.text);
|
|
field.text = "";
|
|
}
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
visible: Polkit.failureText !== ""
|
|
text: Polkit.failureText
|
|
color: Theme.danger
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
spacing: 8
|
|
|
|
SettingsButton {
|
|
text: "Cancel"
|
|
onClicked: Polkit.cancel()
|
|
}
|
|
|
|
SettingsButton {
|
|
text: Polkit.authenticating ? "Checking…" : "Authenticate"
|
|
tone: "accent"
|
|
enabled: !Polkit.authenticating
|
|
onClicked: {
|
|
if (field.text !== "")
|
|
Polkit.submit(field.text);
|
|
field.text = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Escape cancels, which is what every other dialog on this desktop does.
|
|
Item {
|
|
anchors.fill: parent
|
|
focus: true
|
|
Keys.onEscapePressed: Polkit.cancel()
|
|
}
|
|
}
|