refactor(login): clean up qr flow and related state transitions

This commit is contained in:
ayn2op 2026-03-04 12:14:46 -05:00
parent c5a867654d
commit f37169d4d5

View file

@ -1,7 +1,6 @@
package login
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
@ -21,7 +20,7 @@ import (
apphttp "github.com/ayn2op/discordo/internal/http"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/tview"
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/utils/httputil"
"github.com/gdamore/tcell/v3"
"github.com/gorilla/websocket"
"github.com/skip2/go-qrcode"
@ -29,8 +28,6 @@ import (
const gatewayURL = "wss://remote-auth-gateway.discord.gg/?v=2"
var endpointRemoteAuthLogin = api.EndpointMe + "/remote-auth/login"
type qrLogin struct {
*tview.TextView
app *tview.Application
@ -169,12 +166,11 @@ func (q *qrLogin) run(ctx context.Context) {
}
encodedPublicKey := base64.StdEncoding.EncodeToString(pubDER)
headers := stdhttp.Header{}
headers.Set("User-Agent", apphttp.BrowserUserAgent)
headers.Set("Origin", "https://discord.com")
q.setText("Connecting to Remote Auth Gateway...\n\nPress Esc to cancel")
headers := apphttp.Headers()
headers.Set("User-Agent", apphttp.BrowserUserAgent)
dialer := websocket.Dialer{
Proxy: stdhttp.ProxyFromEnvironment,
HandshakeTimeout: 15 * time.Second,
@ -398,55 +394,31 @@ func exchangeTicket(ctx context.Context, ticket string, fingerprint string, priv
if ticket == "" {
return "", errors.New("empty ticket")
}
body := map[string]string{"ticket": ticket}
raw, err := json.Marshal(body)
if err != nil {
return "", err
}
req, err := stdhttp.NewRequestWithContext(ctx, stdhttp.MethodPost, endpointRemoteAuthLogin, bytes.NewReader(raw))
if err != nil {
return "", err
}
req.Header = apphttp.Headers()
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", apphttp.BrowserUserAgent)
headers := apphttp.Headers()
if fingerprint != "" {
req.Header.Set("X-Fingerprint", fingerprint)
req.Header.Set("Referer", "https://discord.com/ra/"+fingerprint)
headers.Set("X-Fingerprint", fingerprint)
headers.Set("Referer", "https://discord.com/ra/"+fingerprint)
}
client := &stdhttp.Client{Transport: apphttp.NewTransport(), Timeout: 20 * time.Second}
resp, err := client.Do(req)
// Create an API client without a token.
client := apphttp.NewClient("")
client.OnRequest = append(client.OnRequest, httputil.WithHeaders(headers))
token, err := client.ExchangeRemoteAuthTicket(ticket)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
b, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("remote-auth login failed: %s: %s", resp.Status, string(b))
}
decoder := json.NewDecoder(resp.Body)
var out struct {
EncryptedToken string `json:"encrypted_token"`
}
if err := decoder.Decode(&out); err != nil {
return "", err
}
if out.EncryptedToken == "" {
return "", fmt.Errorf("no encrypted_token in response")
}
enc, err := base64.StdEncoding.DecodeString(out.EncryptedToken)
decodedToken, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return "", err
}
pt, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, enc, nil)
decryptedToken, err := rsa.DecryptOAEP(sha256.New(), nil, priv, decodedToken, nil)
if err != nil {
return "", err
}
return string(pt), nil
return string(decryptedToken), nil
}
func (q *qrLogin) success(token string) {
@ -463,9 +435,8 @@ func (q *qrLogin) fail(err error) {
}
func mapWSCloseError(err error) error {
var cerr *websocket.CloseError
if errors.As(err, &cerr) {
switch cerr.Code {
if err, ok := errors.AsType[*websocket.CloseError](err); ok {
switch err.Code {
case 1000:
return errors.New("session closed")
case 4000: