Discord sign-in: the browser route is the password route, and says so

The choice reads QR code / Password, and the Password side signs in on
discord.com in a window moho opens - 'Sign in on discord.com' - saying that
this is where an email or phone and password go. The two-factor prompt and
its state are gone with the daemon flow they answered: Discord's own page
asks for the code now.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-01 19:25:21 -04:00
co-authored by Claude Opus 5.5
parent e46bf2a919
commit 035fb73854
3 changed files with 12 additions and 88 deletions
+1 -1
Submodule nobilis updated: 3a5b747401...c3c399dc2b
+10 -67
View File
@@ -950,11 +950,9 @@ function DiscordForm({ accountId }: { accountId?: string }): JSX.Element {
const store = useStore()
const qrPath = useChat((s) => s.discordQrPath)
const status = useChat((s) => s.discordLoginStatus)
const mfa = useChat((s) => s.discordMfa)
const [method, setMethod] = useState<'qr' | 'browser'>('qr')
// No password state any more, deliberately: the password is typed into
// Discord's own page in the sign-in window, so this process never holds one.
const [code, setCode] = useState('')
const [busy, setBusy] = useState(false)
// Only for a new account: re-authenticating keeps the account's own setting.
const [useTor, setUseTor] = useState(false)
@@ -965,65 +963,6 @@ function DiscordForm({ accountId }: { accountId?: string }): JSX.Element {
setBusy(false)
}
// The two-factor step replaces the form: the login is already in flight and
// only needs the code to finish.
if (mfa) {
return (
<div className="add-form">
<p className="small muted">
{mfa.totp
? 'Enter the 6-digit code from your authenticator app.'
: 'Enter your verification code.'}
{mfa.backup && ' A backup code works here too.'}
</p>
<div className="field-row">
<input
className="text-field"
autoFocus
inputMode="numeric"
autoComplete="one-time-code"
placeholder="123456"
value={code}
onChange={(e) => setCode(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && code.trim()) {
void window.moho
.rpc('submitDiscordMfa', { loginId: mfa.loginId, code })
.catch(fail)
}
}}
/>
<button
type="button"
className="button"
disabled={!code.trim()}
onClick={() =>
void window.moho.rpc('submitDiscordMfa', { loginId: mfa.loginId, code }).catch(fail)
}
>
Verify
</button>
</div>
{/* When re-authenticating, the surrounding block already offers a
Cancel that backs out of the whole thing - two stacked Cancels
would just be a question of which one you meant. */}
{!accountId && (
<button
type="button"
className="button subtle"
onClick={() => {
store.clearDiscordMfa()
setCode('')
}}
>
Cancel
</button>
)}
{status && <p className="small muted">{status}</p>}
</div>
)
}
return (
<div className="add-form">
{!accountId && <TorSwitch service="discord" on={useTor} onChange={setUseTor} />}
@@ -1040,7 +979,7 @@ function DiscordForm({ accountId }: { accountId?: string }): JSX.Element {
className={method === 'browser' ? 'active' : undefined}
onClick={() => setMethod('browser')}
>
Browser
Password
</button>
</div>
@@ -1069,11 +1008,15 @@ function DiscordForm({ accountId }: { accountId?: string }): JSX.Element {
</>
) : (
<>
{/* The password route, and the only one: a password typed into a
form of moho's own was tried and removed, because Discord
refuses it from a third-party client and counts each attempt
against the account. */}
<p className="small muted">
Opens Discord&apos;s own sign-in page in a browser window. Your password goes into
their form and never passes through moho — and because it is their page, their
captcha, two-factor and device checks all work normally. The window is thrown away
afterwards, session and all.
Sign in with your email or phone and password on Discord&apos;s own page, in a window
moho opens. Your password goes into their form and never passes through moho — and
because it is their page, their captcha, two-factor and new-device checks all work as
they do anywhere. The window is thrown away afterwards, session and all.
</p>
<button
type="button"
@@ -1095,7 +1038,7 @@ function DiscordForm({ accountId }: { accountId?: string }): JSX.Element {
.catch(fail)
}}
>
{busy ? 'Waiting…' : accountId ? 'Re-authenticate in a browser' : 'Sign in with a browser'}
{busy ? 'Waiting…' : accountId ? 'Re-authenticate on discord.com' : 'Sign in on discord.com'}
</button>
</>
)}
+1 -20
View File
@@ -637,8 +637,6 @@ export interface ChatState {
// Login flows
discordQrPath: string
discordLoginStatus: string
/** Set when a Discord password login stops at the two-factor step. */
discordMfa: { loginId: string; totp: boolean; sms: boolean; backup: boolean } | null
/** The account being re-authenticated, or '' for a brand-new one. */
discordReauthAccountId: string
sneedChatLoginStatus: string
@@ -727,7 +725,6 @@ const INITIAL: ChatState = {
matrixVerification: null,
discordQrPath: '',
discordLoginStatus: '',
discordMfa: null,
discordReauthAccountId: '',
sneedChatLoginStatus: '',
matrixLoginStatus: ''
@@ -3278,24 +3275,12 @@ export class ChatStore {
case 'discordLoginScanned':
this.set({ discordLoginStatus: 'Scanned - approve it on your phone' })
break
case 'discordLoginMfa':
this.set({
discordMfa: {
loginId: data.loginId,
totp: !!data.totp,
sms: !!data.sms,
backup: !!data.backup
},
discordLoginStatus: ''
})
break
case 'discordLoginResult':
this.set({
discordQrPath: '',
discordLoginStatus: data.error || '',
// A failure leaves the form open to try again; success clears it.
discordMfa: data.error ? this.state.discordMfa : null,
discordReauthAccountId: data.error ? this.state.discordReauthAccountId : ''
})
if (!data.error) {
@@ -4538,14 +4523,10 @@ export class ChatStore {
this.set({
discordReauthAccountId: accountId,
discordQrPath: '',
discordLoginStatus: '',
discordMfa: null
discordLoginStatus: ''
})
}
clearDiscordMfa(): void {
this.set({ discordMfa: null })
}
setSneedChatLoginStatus(status: string): void {
this.set({ sneedChatLoginStatus: status })
}