From baf113fb03296e641ec951fd97036f2e9a4ec9dc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 26 Jun 2024 14:43:43 +0300 Subject: [PATCH] v2: add backwards-compatible login API --- cmd/mautrix-signal-v2/legacyprovision.go | 153 ++++++++++++++++++++++- cmd/mautrix-signal-v2/main.go | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/connector/login.go | 1 + 5 files changed, 153 insertions(+), 9 deletions(-) diff --git a/cmd/mautrix-signal-v2/legacyprovision.go b/cmd/mautrix-signal-v2/legacyprovision.go index 5b8e2038..87140b94 100644 --- a/cmd/mautrix-signal-v2/legacyprovision.go +++ b/cmd/mautrix-signal-v2/legacyprovision.go @@ -17,27 +17,170 @@ package main import ( + "encoding/json" "fmt" "net/http" + "strconv" + "sync" + "sync/atomic" "github.com/gorilla/mux" "github.com/rs/zerolog" - - "go.mau.fi/mautrix-signal/legacyprovision" "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridgev2" + + "go.mau.fi/mautrix-signal/legacyprovision" ) +var legacyProvisionHandleID atomic.Uint32 +var loginSessions = make(map[uint32]*legacyLoginProcess) +var loginSessionsLock sync.Mutex + +type legacyLoginProcess struct { + ID uint32 + Login bridgev2.LoginProcess + User *bridgev2.User + Done *bridgev2.UserLogin +} + +func (llp *legacyLoginProcess) Delete() { + loginSessionsLock.Lock() + delete(loginSessions, llp.ID) + loginSessionsLock.Unlock() +} + func legacyProvLinkNew(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) + handleID := legacyProvisionHandleID.Add(1) + user := m.Matrix.Provisioning.GetUser(r) + defLogin := user.GetDefaultLogin() + if defLogin != nil && defLogin.Client != nil && defLogin.Client.IsLoggedIn() { + legacyprovision.JSONResponse(w, http.StatusConflict, &legacyprovision.Error{ + Error: "Already logged in", + ErrCode: "FI.MAU.ALREADY_LOGGED_IN", + }) + return + } + log := zerolog.Ctx(r.Context()) + login, err := m.Connector.CreateLogin(r.Context(), user, "qr") + if err != nil { + log.Err(err).Msg("Failed to create login") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, &legacyprovision.Error{ + Error: "Internal error starting login", + ErrCode: "M_UNKNOWN", + }) + return + } + firstStep, err := login.Start(r.Context()) + if err != nil { + log.Err(err).Msg("Failed to start login") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, &legacyprovision.Error{ + Error: "Internal error starting login", + ErrCode: "M_UNKNOWN", + }) + return + } else if 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", + ErrCode: "M_UNKNOWN", + }) + return + } + loginSessionsLock.Lock() + loginSessions[handleID] = &legacyLoginProcess{ + ID: handleID, + Login: login, + User: user, + } + loginSessionsLock.Unlock() + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ + Success: true, + Status: "provisioning_url_received", + SessionID: strconv.Itoa(int(handleID)), + URI: firstStep.DisplayAndWaitParams.Data, + }) +} + +func getLoginProcess(w http.ResponseWriter, r *http.Request) *legacyLoginProcess { + var body legacyprovision.LinkWaitForAccountRequest + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ + Success: false, + Error: "Error decoding JSON body", + ErrCode: mautrix.MBadJSON.ErrCode, + }) + return nil + } + sessionID, err := strconv.Atoi(body.SessionID) + if err != nil { + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ + Success: false, + Error: "Error decoding session ID in JSON body", + ErrCode: mautrix.MBadJSON.ErrCode, + }) + return nil + } + process, ok := loginSessions[uint32(sessionID)] + user := m.Matrix.Provisioning.GetUser(r) + if !ok || process.User != user { + legacyprovision.JSONResponse(w, http.StatusNotFound, legacyprovision.Error{ + Success: false, + Error: "No session found", + ErrCode: mautrix.MNotFound.ErrCode, + }) + return nil + } + return process } func legacyProvLinkWaitScan(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) + login := getLoginProcess(w, r) + if login == nil { + return + } + res, err := login.Login.(bridgev2.LoginProcessDisplayAndWait).Wait(r.Context()) + if err != nil { + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ + Error: "Failed to log in", + ErrCode: "M_UNKNOWN", + }) + login.Delete() + return + } else if res.Type != bridgev2.LoginStepTypeComplete { + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ + Error: "Unexpected login step", + ErrCode: "M_UNKNOWN", + }) + login.Delete() + return + } + login.Done = res.CompleteParams.UserLogin + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ + Success: true, + Status: "provisioning_data_received", + }) } func legacyProvLinkWaitAccount(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) + login := getLoginProcess(w, r) + 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, + }) + login.Delete() + } else { + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ + Error: "Login not completed", + ErrCode: "M_UNKNOWN", + }) + } } func legacyProvLogout(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/mautrix-signal-v2/main.go b/cmd/mautrix-signal-v2/main.go index 7c0762c5..0e85c385 100644 --- a/cmd/mautrix-signal-v2/main.go +++ b/cmd/mautrix-signal-v2/main.go @@ -51,7 +51,7 @@ func main() { 20, "v0.5.1", "v0.7.0", - m.LegacyMigrateSimple(legacyMigrateRenameTables, legacyMigrateCopyData), + m.LegacyMigrateSimple(legacyMigrateRenameTables, legacyMigrateCopyData, 2), true, ) } diff --git a/go.mod b/go.mod index a59527ce..1462e0b5 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.20240625184543-3f8bb2fd54b1 + maunium.net/go/mautrix v0.19.0-beta.1.0.20240626114248-5dbfd7093e58 nhooyr.io/websocket v1.8.11 ) diff --git a/go.sum b/go.sum index 40f1dee1..be925ba9 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.20240625184543-3f8bb2fd54b1 h1:V+PMBFKZwUA0Jy5DrLh1TtDZhnagQMFN9/2/m3T+SLc= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240625184543-3f8bb2fd54b1/go.mod h1:pFbqAannSyJnohVycF4NW3IngBLWUt/f9KYfJcwyQec= +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= 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 4205f672..824f8f13 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -174,6 +174,7 @@ func (qr *QRLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { Instructions: fmt.Sprintf("Successfully logged in as %s / %s", signalPhone, signalID), CompleteParams: &bridgev2.LoginCompleteParams{ UserLoginID: ul.ID, + UserLogin: ul, }, }, nil }