From 72e9cc6c11ff8e0e52aae68a5e1d44702e7d9971 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 26 Jun 2024 20:09:00 +0300 Subject: [PATCH] v2: add extra wait step to login --- cmd/mautrix-signal-v2/legacyprovision.go | 35 +++++++++------ go.mod | 2 +- go.sum | 4 +- pkg/connector/login.go | 55 ++++++++++++++++++------ 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/cmd/mautrix-signal-v2/legacyprovision.go b/cmd/mautrix-signal-v2/legacyprovision.go index 87140b94..007cf33b 100644 --- a/cmd/mautrix-signal-v2/legacyprovision.go +++ b/cmd/mautrix-signal-v2/legacyprovision.go @@ -30,6 +30,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "go.mau.fi/mautrix-signal/legacyprovision" + "go.mau.fi/mautrix-signal/pkg/connector" ) var legacyProvisionHandleID atomic.Uint32 @@ -40,7 +41,6 @@ type legacyLoginProcess struct { ID uint32 Login bridgev2.LoginProcess User *bridgev2.User - Done *bridgev2.UserLogin } func (llp *legacyLoginProcess) Delete() { @@ -78,7 +78,7 @@ func legacyProvLinkNew(w http.ResponseWriter, r *http.Request) { ErrCode: "M_UNKNOWN", }) return - } else if firstStep.Type != bridgev2.LoginStepTypeDisplayAndWait || firstStep.DisplayAndWaitParams.Type != bridgev2.LoginDisplayTypeQR { + } else if firstStep.StepID != connector.LoginStepQR || firstStep.Type != bridgev2.LoginStepTypeDisplayAndWait || firstStep.DisplayAndWaitParams.Type != bridgev2.LoginDisplayTypeQR { log.Error().Any("first_step", firstStep).Msg("Unexpected first step") legacyprovision.JSONResponse(w, http.StatusInternalServerError, &legacyprovision.Error{ Error: "Unexpected first login step", @@ -141,13 +141,15 @@ func legacyProvLinkWaitScan(w http.ResponseWriter, r *http.Request) { } res, err := login.Login.(bridgev2.LoginProcessDisplayAndWait).Wait(r.Context()) if err != nil { + zerolog.Ctx(r.Context()).Err(err).Msg("Failed to log in") legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Error: "Failed to log in", ErrCode: "M_UNKNOWN", }) login.Delete() return - } else if res.Type != bridgev2.LoginStepTypeComplete { + } else if res.StepID != connector.LoginStepProcess { + zerolog.Ctx(r.Context()).Error().Any("first_step", res).Msg("Unexpected login step") legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Error: "Unexpected login step", ErrCode: "M_UNKNOWN", @@ -155,7 +157,6 @@ func legacyProvLinkWaitScan(w http.ResponseWriter, r *http.Request) { login.Delete() return } - login.Done = res.CompleteParams.UserLogin legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ Success: true, Status: "provisioning_data_received", @@ -167,20 +168,28 @@ func legacyProvLinkWaitAccount(w http.ResponseWriter, r *http.Request) { if login == nil { return } - if login.Done != nil { - legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ - Success: true, - Status: "prekeys_registered", - UUID: string(login.Done.ID), - Number: login.Done.Metadata.RemoteName, + res, err := login.Login.(bridgev2.LoginProcessDisplayAndWait).Wait(r.Context()) + if err != nil { + zerolog.Ctx(r.Context()).Err(err).Msg("Failed to log in") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ + Error: "Failed to log in", + ErrCode: "M_UNKNOWN", }) - login.Delete() - } else { + } else if res.StepID != connector.LoginStepComplete || res.Type != bridgev2.LoginStepTypeComplete { + zerolog.Ctx(r.Context()).Error().Any("first_step", res).Msg("Unexpected login step") legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ - Error: "Login not completed", + Error: "Unexpected login step", ErrCode: "M_UNKNOWN", }) + } else { + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ + Success: true, + Status: "prekeys_registered", + UUID: string(res.CompleteParams.UserLogin.ID), + Number: res.CompleteParams.UserLogin.Metadata.RemoteName, + }) } + login.Delete() } func legacyProvLogout(w http.ResponseWriter, r *http.Request) { diff --git a/go.mod b/go.mod index 1462e0b5..af271ebe 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/net v0.26.0 google.golang.org/protobuf v1.34.2 - maunium.net/go/mautrix v0.19.0-beta.1.0.20240626114248-5dbfd7093e58 + maunium.net/go/mautrix v0.19.0-beta.1.0.20240626170142-1a18d9ee55f1 nhooyr.io/websocket v1.8.11 ) diff --git a/go.sum b/go.sum index be925ba9..43b73090 100644 --- a/go.sum +++ b/go.sum @@ -93,7 +93,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240626114248-5dbfd7093e58 h1:N2Mll8zmQPEFhLgJdH/QPw9Wu4nnLD1LIrAyk0vohHo= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240626114248-5dbfd7093e58/go.mod h1:pFbqAannSyJnohVycF4NW3IngBLWUt/f9KYfJcwyQec= +maunium.net/go/mautrix v0.19.0-beta.1.0.20240626170142-1a18d9ee55f1 h1:iE/MzeUGHAs2pbtq/hVnJp7pFQhgjRnZZFfnFDwZdq0= +maunium.net/go/mautrix v0.19.0-beta.1.0.20240626170142-1a18d9ee55f1/go.mod h1:pFbqAannSyJnohVycF4NW3IngBLWUt/f9KYfJcwyQec= nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 824f8f13..24cbcd71 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -26,6 +26,7 @@ import ( "maunium.net/go/mautrix/bridgev2/database" "go.mau.fi/mautrix-signal/pkg/signalmeow" + "go.mau.fi/mautrix-signal/pkg/signalmeow/store" ) func (s *SignalConnector) GetLoginFlows() []bridgev2.LoginFlow { @@ -48,6 +49,8 @@ type QRLogin struct { Main *SignalConnector cancelChan context.CancelFunc ProvChan chan signalmeow.ProvisioningResponse + + ProvData *store.DeviceData } var _ bridgev2.LoginProcessDisplayAndWait = (*QRLogin)(nil) @@ -60,6 +63,12 @@ func (qr *QRLogin) Cancel() { }() } +const ( + LoginStepQR = "fi.mau.signal.login.qr" + LoginStepProcess = "fi.mau.signal.login.processing" + LoginStepComplete = "fi.mau.signal.login.complete" +) + func (qr *QRLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { log := qr.Main.Bridge.Log.With(). Str("action", "login"). @@ -84,7 +93,7 @@ func (qr *QRLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { } return &bridgev2.LoginStep{ Type: bridgev2.LoginStepTypeDisplayAndWait, - StepID: "fi.mau.signal.login.qr", + StepID: LoginStepQR, Instructions: "Scan the QR code on your Signal app to log in", DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{ Type: bridgev2.LoginDisplayTypeQR, @@ -97,25 +106,45 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { if qr.ProvChan == nil { return nil, fmt.Errorf("login not started") } - defer qr.cancelChan() - var signalID uuid.UUID - var signalPhone string + if qr.ProvData == nil { + return qr.qrWait(ctx) + } else { + return qr.processingWait(ctx) + } +} + +func (qr *QRLogin) qrWait(ctx context.Context) (*bridgev2.LoginStep, error) { select { case resp := <-qr.ProvChan: if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { + qr.cancelChan() return nil, resp.Err } else if resp.State != signalmeow.StateProvisioningDataReceived { + qr.cancelChan() return nil, fmt.Errorf("unexpected state %v", resp.State) } else if resp.ProvisioningData.ACI == uuid.Nil { + qr.cancelChan() return nil, fmt.Errorf("no signal account ID received") } - signalID = resp.ProvisioningData.ACI - signalPhone = resp.ProvisioningData.Number + qr.ProvData = resp.ProvisioningData + return &bridgev2.LoginStep{ + Type: bridgev2.LoginStepTypeDisplayAndWait, + StepID: LoginStepProcess, + Instructions: fmt.Sprintf("Processing login as %s...", resp.ProvisioningData.Number), + DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{ + Type: bridgev2.LoginDisplayTypeNothing, + }, + }, nil case <-ctx.Done(): + qr.cancelChan() return nil, ctx.Err() } - newLoginID := makeUserLoginID(signalID) +} + +func (qr *QRLogin) processingWait(ctx context.Context) (*bridgev2.LoginStep, error) { + defer qr.cancelChan() + newLoginID := makeUserLoginID(qr.ProvData.ACI) select { case resp := <-qr.ProvChan: @@ -141,10 +170,10 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { ID: newLoginID, Metadata: database.UserLoginMetadata{ StandardUserLoginMetadata: database.StandardUserLoginMetadata{ - RemoteName: signalPhone, + RemoteName: qr.ProvData.Number, }, Extra: map[string]any{ - "phone": signalPhone, + "phone": qr.ProvData.Number, }, }, }, nil) @@ -152,8 +181,8 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { return nil, fmt.Errorf("failed to save new login: %w", err) } } else { - ul.Metadata.Extra["phone"] = signalPhone - ul.Metadata.RemoteName = signalPhone + ul.Metadata.Extra["phone"] = qr.ProvData.Number + ul.Metadata.RemoteName = qr.ProvData.Number err = ul.Save(ctx) if err != nil { return nil, fmt.Errorf("failed to update existing login: %w", err) @@ -170,8 +199,8 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { } return &bridgev2.LoginStep{ Type: bridgev2.LoginStepTypeComplete, - StepID: "fi.mau.signal.login.complete", - Instructions: fmt.Sprintf("Successfully logged in as %s / %s", signalPhone, signalID), + StepID: LoginStepComplete, + Instructions: fmt.Sprintf("Successfully logged in as %s / %s", qr.ProvData.Number, qr.ProvData.ACI), CompleteParams: &bridgev2.LoginCompleteParams{ UserLoginID: ul.ID, UserLogin: ul,