diff --git a/nobilis b/nobilis
index e0c66ca..d4b6ab8 160000
--- a/nobilis
+++ b/nobilis
@@ -1 +1 @@
-Subproject commit e0c66ca0c98966f5f382d47c675e5444df732834
+Subproject commit d4b6ab89f3102b6dacb6a00f75021ecdb70cec6d
diff --git a/src/renderer/src/app.css b/src/renderer/src/app.css
index 99d4124..afb8aeb 100644
--- a/src/renderer/src/app.css
+++ b/src/renderer/src/app.css
@@ -816,6 +816,21 @@ img.composer-emoji {
gap: var(--spacing-s);
}
+/* The SASL fields read as one decision rather than three loose settings, so
+ they are boxed together and indent under the switch that turns them on. */
+.sasl-block {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-s);
+ padding: var(--spacing-s);
+ border: 1px solid var(--outline-light);
+ border-radius: var(--corner-radius);
+}
+
+.sasl-block .field {
+ margin-left: calc(16px + var(--spacing-s));
+}
+
.qr-image {
width: 220px;
height: 220px;
diff --git a/src/renderer/src/components/AccountsPanel.tsx b/src/renderer/src/components/AccountsPanel.tsx
index 566c5e8..abea1d6 100644
--- a/src/renderer/src/components/AccountsPanel.tsx
+++ b/src/renderer/src/components/AccountsPanel.tsx
@@ -274,6 +274,7 @@ function AccountRow({ account }: { account: Account }): JSX.Element {
call('setAccountAutojoin', { accountId: account.id, channels })
}
/>
+ ) => void
+}): JSX.Element {
+ const [username, setUsername] = useState(account.saslUsername)
+ const [password, setPassword] = useState('')
+ const [allowPlaintext, setAllowPlaintext] = useState(account.allowPlaintextSasl)
+
+ const apply = (patch: { enabled?: boolean; saslUser?: string; allowPlaintextSasl?: boolean }): void =>
+ call('setAccountSasl', {
+ accountId: account.id,
+ enabled: account.saslEnabled,
+ saslUser: username,
+ // Empty means "leave the stored one alone" - the daemon never reads a
+ // password back out, so there is nothing to prefill and clearing the
+ // box must not be read as clearing the credential.
+ password,
+ allowPlaintextSasl: allowPlaintext,
+ ...patch
+ })
+
+ return (
+
+
+
+ {account.saslEnabled && (
+ <>
+ {
+ setUsername(value)
+ apply({ saslUser: value })
+ }}
+ />
+ {
+ setPassword(value)
+ call('setAccountSasl', {
+ accountId: account.id,
+ enabled: true,
+ saslUser: username,
+ password: value,
+ allowPlaintextSasl: allowPlaintext
+ })
+ }}
+ />
+
+ {/* Only where it is a live question. On a TLS connection the
+ allowance decides nothing, and offering it there would be a
+ switch that appears to weaken something and does not. */}
+ {!account.ssl && (
+
+ )}
+ >
+ )}
+
+ )
+}
+
function LabeledInput({
label,
defaultValue,
diff --git a/src/shared/wire.ts b/src/shared/wire.ts
index 251992c..1965e92 100644
--- a/src/shared/wire.ts
+++ b/src/shared/wire.ts
@@ -16,6 +16,11 @@ export interface Account {
saslEnabled: boolean
saslUsername: string
allowPlaintextSasl: boolean
+ /**
+ * Whether the connection is encrypted. Only IRC is ever false - every other
+ * service here is HTTPS or WSS by construction.
+ */
+ ssl: boolean
hasPassword: boolean
avatarUrl?: string
sockchatRooms?: SockChatRoom[]