Offer the SASL settings the pane already claimed to have
The daemon has done SASL for a long time. There was simply no way to turn it on: the account card offered autojoin, a NickServ password and the proxy switch, and nothing else - while the settings pane told people that "SASL, autojoin and NickServ auto-identify are per-account instead, from that account's own entry in the Accounts pane". Two of those three were true. So the card carries them now, behind one switch because they are one decision, and sent as one call because the daemon stores them together - a username applied without the password it goes with is a state that could decide a connection attempt on its own. The plaintext allowance only appears on a connection that has no encryption, which the daemon now reports. On a TLS account it decides nothing, and a switch that appears to weaken something while doing nothing is worse than no switch. Where it does appear it says what it actually means: SASL PLAIN is the password with base64 round it, and without the allowance connecting is refused rather than done quietly in the clear. Submodule moves to the matching daemon.
This commit is contained in:
+1
-1
Submodule nobilis updated: e0c66ca0c9...d4b6ab89f3
@@ -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;
|
||||
|
||||
@@ -274,6 +274,7 @@ function AccountRow({ account }: { account: Account }): JSX.Element {
|
||||
call('setAccountAutojoin', { accountId: account.id, channels })
|
||||
}
|
||||
/>
|
||||
<IrcSasl account={account} call={call} />
|
||||
<LabeledInput
|
||||
label="NickServ password"
|
||||
type="password"
|
||||
@@ -322,6 +323,115 @@ function AccountRow({ account }: { account: Account }): JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Signing in to an IRC network with SASL rather than by messaging NickServ.
|
||||
*
|
||||
* Worth preferring where it exists: it happens during registration, so the
|
||||
* account is identified before the first channel is joined - which is what
|
||||
* makes cloaks and invite-only channels work on the first attempt instead of
|
||||
* after a race with NickServ that IRC gives no way to wait on.
|
||||
*
|
||||
* All four fields are one settings call, because the daemon stores them
|
||||
* together and sending them apart would let a half-applied change - a username
|
||||
* without the password it goes with - decide a connection attempt.
|
||||
*/
|
||||
function IrcSasl({
|
||||
account,
|
||||
call
|
||||
}: {
|
||||
account: Account
|
||||
call: (method: string, params: Record<string, unknown>) => 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 (
|
||||
<div className="sasl-block">
|
||||
<label className="checkbox-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={account.saslEnabled}
|
||||
onChange={(e) => apply({ enabled: e.target.checked })}
|
||||
/>
|
||||
<span>
|
||||
Authenticate with SASL
|
||||
<span className="small muted">
|
||||
{' '}
|
||||
— identifies during connection, before any channel is joined
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{account.saslEnabled && (
|
||||
<>
|
||||
<LabeledInput
|
||||
label="SASL username"
|
||||
defaultValue={account.saslUsername}
|
||||
placeholder={account.id.split('@')[0] || 'your account name'}
|
||||
onCommit={(value) => {
|
||||
setUsername(value)
|
||||
apply({ saslUser: value })
|
||||
}}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SASL password"
|
||||
type="password"
|
||||
placeholder={account.hasPassword ? '(set)' : 'unset'}
|
||||
onCommit={(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 && (
|
||||
<label className="checkbox-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={account.allowPlaintextSasl}
|
||||
onChange={(e) => {
|
||||
setAllowPlaintext(e.target.checked)
|
||||
apply({ allowPlaintextSasl: e.target.checked })
|
||||
}}
|
||||
/>
|
||||
<span>
|
||||
Send SASL credentials over this unencrypted connection
|
||||
<span className="small muted">
|
||||
{' '}
|
||||
— SASL PLAIN is the password with base64 round it, not encryption. Without
|
||||
this, connecting is refused rather than done quietly in the clear.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LabeledInput({
|
||||
label,
|
||||
defaultValue,
|
||||
|
||||
@@ -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[]
|
||||
|
||||
Reference in New Issue
Block a user