diff --git a/cmd/mautrix-signal-v2/legacyprovision.go b/cmd/mautrix-signal-v2/legacyprovision.go new file mode 100644 index 00000000..5b8e2038 --- /dev/null +++ b/cmd/mautrix-signal-v2/legacyprovision.go @@ -0,0 +1,133 @@ +// mautrix-signal - A Matrix-Signal puppeting bridge. +// Copyright (C) 2024 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is istributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package main + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/rs/zerolog" + + "go.mau.fi/mautrix-signal/legacyprovision" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2" +) + +func legacyProvLinkNew(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +func legacyProvLinkWaitScan(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +func legacyProvLinkWaitAccount(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +func legacyProvLogout(w http.ResponseWriter, r *http.Request) { + user := m.Matrix.Provisioning.GetUser(r) + for { + login := user.GetDefaultLogin() + if login == nil { + break + } + login.Logout(r.Context()) + } + legacyprovision.JSONResponse(w, http.StatusOK, nil) +} + +func legacyResolveIdentifierOrStartChat(w http.ResponseWriter, r *http.Request, create bool) { + login := m.Matrix.Provisioning.GetLoginForRequest(w, r) + if login == nil { + return + } + api := login.Client.(bridgev2.IdentifierResolvingNetworkAPI) + resp, err := api.ResolveIdentifier(r.Context(), mux.Vars(r)["phonenum"], create) + if err != nil { + zerolog.Ctx(r.Context()).Err(err).Msg("Failed to resolve identifier") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, &legacyprovision.Error{ + Error: fmt.Sprintf("Failed to resolve identifier: %v", err), + ErrCode: "M_UNKNOWN", + }) + return + } else if resp == nil { + legacyprovision.JSONResponse(w, http.StatusNotFound, &legacyprovision.Error{ + ErrCode: mautrix.MNotFound.ErrCode, + Error: "User not found on Signal", + }) + return + } + status := http.StatusOK + apiResp := &legacyprovision.ResolveIdentifierResponse{ + ChatID: legacyprovision.ResolveIdentifierResponseChatID{ + UUID: string(resp.UserID), + Number: "", + }, + } + if resp.Ghost != nil { + if resp.UserInfo != nil { + resp.Ghost.UpdateInfo(r.Context(), resp.UserInfo) + } + apiResp.OtherUser = &legacyprovision.ResolveIdentifierResponseOtherUser{ + MXID: resp.Ghost.Intent.GetMXID(), + DisplayName: resp.Ghost.Name, + AvatarURL: resp.Ghost.AvatarMXC.ParseOrIgnore(), + } + } + if resp.Chat != nil { + if resp.Chat.Portal == nil { + resp.Chat.Portal, err = m.Bridge.GetPortalByID(r.Context(), resp.Chat.PortalID) + if err != nil { + zerolog.Ctx(r.Context()).Err(err).Msg("Failed to get portal") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, &mautrix.RespError{ + Err: "Failed to get portal", + ErrCode: "M_UNKNOWN", + }) + return + } + } + if create && resp.Chat.Portal.MXID == "" { + apiResp.JustCreated = true + status = http.StatusCreated + err = resp.Chat.Portal.CreateMatrixRoom(r.Context(), login, resp.Chat.PortalInfo) + if err != nil { + zerolog.Ctx(r.Context()).Err(err).Msg("Failed to create portal room") + legacyprovision.JSONResponse(w, http.StatusInternalServerError, &mautrix.RespError{ + Err: "Failed to create portal room", + ErrCode: "M_UNKNOWN", + }) + return + } + } + apiResp.RoomID = resp.Chat.Portal.MXID + } + legacyprovision.JSONResponse(w, status, &legacyprovision.Response{ + Success: true, + Status: "ok", + ResolveIdentifierResponse: apiResp, + }) +} + +func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) { + legacyResolveIdentifierOrStartChat(w, r, false) +} + +func legacyProvPM(w http.ResponseWriter, r *http.Request) { + legacyResolveIdentifierOrStartChat(w, r, true) +} diff --git a/cmd/mautrix-signal-v2/main.go b/cmd/mautrix-signal-v2/main.go index bc55d45b..7c0762c5 100644 --- a/cmd/mautrix-signal-v2/main.go +++ b/cmd/mautrix-signal-v2/main.go @@ -17,6 +17,8 @@ package main import ( + "net/http" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" "maunium.net/go/mautrix/bridgev2/matrix/mxmain" @@ -53,6 +55,16 @@ func main() { true, ) } + m.PostStart = func() { + if m.Matrix.Provisioning != nil { + m.Matrix.Provisioning.Router.HandleFunc("/v2/link/new", legacyProvLinkNew).Methods(http.MethodPost) + m.Matrix.Provisioning.Router.HandleFunc("/v2/link/wait/scan", legacyProvLinkWaitScan).Methods(http.MethodPost) + m.Matrix.Provisioning.Router.HandleFunc("/v2/link/wait/account", legacyProvLinkWaitAccount).Methods(http.MethodPost) + m.Matrix.Provisioning.Router.HandleFunc("/v2/logout", legacyProvLogout).Methods(http.MethodPost) + m.Matrix.Provisioning.Router.HandleFunc("/v2/resolve_identifier/{phonenum}", legacyProvResolveIdentifier).Methods(http.MethodGet) + m.Matrix.Provisioning.Router.HandleFunc("/v2/pm/{phonenum}", legacyProvPM).Methods(http.MethodPost) + } + } m.InitVersion(Tag, Commit, BuildTime) m.Run() } diff --git a/go.mod b/go.mod index 046efb63..0f99be49 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.20240625104456-54ff874fac72 + maunium.net/go/mautrix v0.19.0-beta.1.0.20240625120422-13b2d6275302 nhooyr.io/websocket v1.8.11 ) diff --git a/go.sum b/go.sum index 3ee63e21..c6356ce7 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.20240625104456-54ff874fac72 h1:66ojF6y1KlyOyPLj6/XVxoj+ex/LmxFoCiR6SzvEFBI= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240625104456-54ff874fac72/go.mod h1:pFbqAannSyJnohVycF4NW3IngBLWUt/f9KYfJcwyQec= +maunium.net/go/mautrix v0.19.0-beta.1.0.20240625120422-13b2d6275302 h1:PbCdso3xltp5ztKXpAW4C4tC8LLgxeHkJ4mF/7HE3Z0= +maunium.net/go/mautrix v0.19.0-beta.1.0.20240625120422-13b2d6275302/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/legacyprovision/types.go b/legacyprovision/types.go new file mode 100644 index 00000000..72f393a0 --- /dev/null +++ b/legacyprovision/types.go @@ -0,0 +1,92 @@ +// mautrix-signal - A Matrix-Signal puppeting bridge. +// Copyright (C) 2024 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is istributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package legacyprovision + +import ( + "encoding/json" + "net/http" + + "maunium.net/go/mautrix/id" +) + +func JSONResponse(w http.ResponseWriter, status int, response any) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(response) +} + +type Error struct { + Success bool `json:"success"` + Error string `json:"error"` + ErrCode string `json:"errcode"` +} + +type Response struct { + Success bool `json:"success"` + Status string `json:"status"` + + // For response in LinkNew + SessionID string `json:"session_id,omitempty"` + URI string `json:"uri,omitempty"` + + // For response in LinkWaitForAccount + UUID string `json:"uuid,omitempty"` + Number string `json:"number,omitempty"` + + // For response in ResolveIdentifier + *ResolveIdentifierResponse +} + +type WhoAmIResponse struct { + Permissions int `json:"permissions"` + MXID string `json:"mxid"` + Signal *WhoAmIResponseSignal `json:"signal,omitempty"` +} + +type WhoAmIResponseSignal struct { + Number string `json:"number"` + UUID string `json:"uuid"` + Name string `json:"name"` + Ok bool `json:"ok"` +} + +type ResolveIdentifierResponse struct { + RoomID id.RoomID `json:"room_id"` + ChatID ResolveIdentifierResponseChatID `json:"chat_id"` + JustCreated bool `json:"just_created"` + OtherUser *ResolveIdentifierResponseOtherUser `json:"other_user,omitempty"` +} + +type ResolveIdentifierResponseChatID struct { + UUID string `json:"uuid"` + Number string `json:"number"` +} + +type ResolveIdentifierResponseOtherUser struct { + MXID id.UserID `json:"mxid"` + DisplayName string `json:"displayname"` + AvatarURL id.ContentURI `json:"avatar_url"` +} + +type LinkWaitForScanRequest struct { + SessionID string `json:"session_id"` +} + +type LinkWaitForAccountRequest struct { + SessionID string `json:"session_id"` + DeviceName string `json:"device_name"` // TODO this seems to not be used anywhere +} diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 7c4c6466..f0f06f30 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -19,7 +19,6 @@ package connector import ( "context" "crypto/sha256" - "errors" "fmt" "strconv" "strings" @@ -148,7 +147,7 @@ func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, cre aci = resp[e164Number].ACI pni = resp[e164Number].PNI if aci == uuid.Nil && pni == uuid.Nil { - return nil, errors.New("user not found on Signal") + return nil, nil } recipient, err = s.Client.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, e164String) if err != nil { diff --git a/provisioning.go b/provisioning.go index 3be384b7..46b52d1f 100644 --- a/provisioning.go +++ b/provisioning.go @@ -36,6 +36,7 @@ import ( "maunium.net/go/mautrix" "maunium.net/go/mautrix/id" + "go.mau.fi/mautrix-signal/legacyprovision" "go.mau.fi/mautrix-signal/pkg/libsignalgo" "go.mau.fi/mautrix-signal/pkg/signalmeow" "go.mau.fi/mautrix-signal/pkg/signalmeow/types" @@ -86,18 +87,12 @@ func (prov *ProvisioningAPI) Init() { } } -func jsonResponse(w http.ResponseWriter, status int, response any) { - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(response) -} - func (prov *ProvisioningAPI) AuthMiddleware(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") if auth != prov.bridge.Config.Bridge.Provisioning.SharedSecret { zerolog.Ctx(r.Context()).Warn().Msg("Authentication token does not match shared secret") - jsonResponse(w, http.StatusForbidden, &mautrix.RespError{ + legacyprovision.JSONResponse(w, http.StatusForbidden, &mautrix.RespError{ Err: "Authentication token does not match shared secret", ErrCode: mautrix.MForbidden.ErrCode, }) @@ -109,60 +104,7 @@ func (prov *ProvisioningAPI) AuthMiddleware(h http.Handler) http.Handler { }) } -type Error struct { - Success bool `json:"success"` - Error string `json:"error"` - ErrCode string `json:"errcode"` -} - -type Response struct { - Success bool `json:"success"` - Status string `json:"status"` - - // For response in LinkNew - SessionID string `json:"session_id,omitempty"` - URI string `json:"uri,omitempty"` - - // For response in LinkWaitForAccount - UUID string `json:"uuid,omitempty"` - Number string `json:"number,omitempty"` - - // For response in ResolveIdentifier - *ResolveIdentifierResponse -} - -type WhoAmIResponse struct { - Permissions int `json:"permissions"` - MXID string `json:"mxid"` - Signal *WhoAmIResponseSignal `json:"signal,omitempty"` -} - -type WhoAmIResponseSignal struct { - Number string `json:"number"` - UUID string `json:"uuid"` - Name string `json:"name"` - Ok bool `json:"ok"` -} - -type ResolveIdentifierResponse struct { - RoomID id.RoomID `json:"room_id"` - ChatID ResolveIdentifierResponseChatID `json:"chat_id"` - JustCreated bool `json:"just_created"` - OtherUser *ResolveIdentifierResponseOtherUser `json:"other_user,omitempty"` -} - -type ResolveIdentifierResponseChatID struct { - UUID string `json:"uuid"` - Number string `json:"number"` -} - -type ResolveIdentifierResponseOtherUser struct { - MXID string `json:"mxid"` - DisplayName string `json:"displayname"` - AvatarURL string `json:"avatar_url"` -} - -func (prov *ProvisioningAPI) resolveIdentifier(ctx context.Context, user *User, inputPhone string) (int, *ResolveIdentifierResponse, error) { +func (prov *ProvisioningAPI) resolveIdentifier(ctx context.Context, user *User, inputPhone string) (int, *legacyprovision.ResolveIdentifierResponse, error) { if user.Client == nil { return http.StatusUnauthorized, nil, errors.New("not currently connected to Signal") } @@ -199,14 +141,14 @@ func (prov *ProvisioningAPI) resolveIdentifier(ctx context.Context, user *User, Msg("Found DM target user") var targetServiceID libsignalgo.ServiceID - var otherUserInfo *ResolveIdentifierResponseOtherUser + var otherUserInfo *legacyprovision.ResolveIdentifierResponseOtherUser if aci != uuid.Nil { targetServiceID = libsignalgo.NewACIServiceID(aci) puppet := prov.bridge.GetPuppetBySignalID(aci) - otherUserInfo = &ResolveIdentifierResponseOtherUser{ - MXID: puppet.MXID.String(), + otherUserInfo = &legacyprovision.ResolveIdentifierResponseOtherUser{ + MXID: puppet.MXID, DisplayName: puppet.Name, - AvatarURL: puppet.AvatarURL.String(), + AvatarURL: puppet.AvatarURL, } } else { targetServiceID = libsignalgo.NewPNIServiceID(pni) @@ -214,9 +156,9 @@ func (prov *ProvisioningAPI) resolveIdentifier(ctx context.Context, user *User, } portal := user.GetPortalByChatID(targetServiceID.String()) - return http.StatusOK, &ResolveIdentifierResponse{ + return http.StatusOK, &legacyprovision.ResolveIdentifierResponse{ RoomID: portal.MXID, - ChatID: ResolveIdentifierResponseChatID{ + ChatID: legacyprovision.ResolveIdentifierResponseChatID{ UUID: targetServiceID.String(), Number: e164String, }, @@ -245,14 +187,14 @@ func (prov *ProvisioningAPI) ResolveIdentifier(w http.ResponseWriter, r *http.Re } else { log.Err(err).Msg("error looking up contact") } - jsonResponse(w, status, Error{ + legacyprovision.JSONResponse(w, status, legacyprovision.Error{ Success: false, Error: err.Error(), ErrCode: errCode, }) return } - jsonResponse(w, status, Response{ + legacyprovision.JSONResponse(w, status, legacyprovision.Response{ Success: true, Status: "ok", ResolveIdentifierResponse: resp, @@ -280,7 +222,7 @@ func (prov *ProvisioningAPI) StartPM(w http.ResponseWriter, r *http.Request) { } else { log.Err(err).Msg("error looking up contact") } - jsonResponse(w, status, Error{ + legacyprovision.JSONResponse(w, status, legacyprovision.Error{ Success: false, Error: err.Error(), ErrCode: errCode, @@ -292,7 +234,7 @@ func (prov *ProvisioningAPI) StartPM(w http.ResponseWriter, r *http.Request) { if portal.MXID == "" { if err := portal.CreateMatrixRoom(r.Context(), user, 0); err != nil { log.Err(err).Msg("error looking up contact") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: "Error creating Matrix room", ErrCode: "M_INTERNAL", @@ -306,7 +248,7 @@ func (prov *ProvisioningAPI) StartPM(w http.ResponseWriter, r *http.Request) { status = http.StatusCreated } - jsonResponse(w, status, Response{ + legacyprovision.JSONResponse(w, status, legacyprovision.Response{ Success: true, Status: "ok", ResolveIdentifierResponse: resp, @@ -401,7 +343,7 @@ func (prov *ProvisioningAPI) checkSessionAndReturnHandle(ctx context.Context, w handle := prov.existingSession(user) if handle == nil { log.Warn().Msg("no session found") - jsonResponse(w, http.StatusNotFound, Error{ + legacyprovision.JSONResponse(w, http.StatusNotFound, legacyprovision.Error{ Success: false, Error: "No session found", ErrCode: "M_NOT_FOUND", @@ -413,7 +355,7 @@ func (prov *ProvisioningAPI) checkSessionAndReturnHandle(ctx context.Context, w Int("handle_id", handle.id). Int("current_session", currentSession). Msg("session_id does not match user's session_id") - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "session_id does not match user's session_id", ErrCode: "M_BAD_JSON", @@ -431,12 +373,12 @@ func (prov *ProvisioningAPI) WhoAmI(w http.ResponseWriter, r *http.Request) { Logger() log.Debug().Msg("getting whoami") - data := WhoAmIResponse{ + data := legacyprovision.WhoAmIResponse{ Permissions: int(user.PermissionLevel), MXID: user.MXID.String(), } if user.IsLoggedIn() { - data.Signal = &WhoAmIResponseSignal{ + data.Signal = &legacyprovision.WhoAmIResponseSignal{ Number: user.SignalUsername, UUID: user.SignalID.String(), Ok: user.Client.IsConnected(), @@ -446,7 +388,7 @@ func (prov *ProvisioningAPI) WhoAmI(w http.ResponseWriter, r *http.Request) { data.Signal.Name = puppet.Name } } - jsonResponse(w, http.StatusOK, data) + legacyprovision.JSONResponse(w, http.StatusOK, data) } func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { @@ -460,7 +402,7 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { handle, err := prov.loginOrSendError(ctx, w, user) if err != nil { - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: err.Error(), ErrCode: "M_INTERNAL", @@ -475,7 +417,7 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { case resp := <-handle.channel: if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { log.Err(resp.Err).Msg("Error getting provisioning URL") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: resp.Err.Error(), ErrCode: "M_INTERNAL", @@ -484,7 +426,7 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { } if resp.State != signalmeow.StateProvisioningURLReceived { log.Err(resp.Err).Stringer("state", resp.State).Msg("unexpected state") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: fmt.Sprintf("Unexpected state %s", resp.State.String()), ErrCode: "M_INTERNAL", @@ -493,7 +435,7 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { } log.Debug().Str("provisioning_url", resp.ProvisioningURL).Msg("provisioning URL received") - jsonResponse(w, http.StatusOK, Response{ + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ Success: true, Status: "provisioning_url_received", SessionID: fmt.Sprintf("%d", handle.id), @@ -501,7 +443,7 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { }) case <-time.After(30 * time.Second): log.Warn().Msg("Timeout waiting for provisioning response (new)") - jsonResponse(w, http.StatusGatewayTimeout, Error{ + legacyprovision.JSONResponse(w, http.StatusGatewayTimeout, legacyprovision.Error{ Success: false, Error: "Timeout waiting for provisioning response (new)", ErrCode: "M_TIMEOUT", @@ -509,17 +451,13 @@ func (prov *ProvisioningAPI) LinkNew(w http.ResponseWriter, r *http.Request) { } } -type LinkWaitForScanRequest struct { - SessionID string `json:"session_id"` -} - func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(provisioningUserKey).(*User) - var body LinkWaitForScanRequest + var body legacyprovision.LinkWaitForScanRequest err := json.NewDecoder(r.Body).Decode(&body) if err != nil { - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "Error decoding JSON body", ErrCode: "M_BAD_JSON", @@ -528,7 +466,7 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ } sessionID, err := strconv.Atoi(body.SessionID) if err != nil { - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "Error decoding session ID in JSON body", ErrCode: "M_BAD_JSON", @@ -561,7 +499,7 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ // If we error waiting for the scan, treat it as a normal error not 5xx // so that the client will retry quietly. Also, it's really not an internal // error, sitting with a WS open waiting for a scan is inherently flaky. - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: resp.Err.Error(), ErrCode: "M_INTERNAL", @@ -570,7 +508,7 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ } if resp.State != signalmeow.StateProvisioningDataReceived { log.Err(resp.Err).Stringer("state", resp.State).Msg("unexpected state") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: fmt.Sprintf("Unexpected state %s", resp.State.String()), ErrCode: "M_INTERNAL", @@ -578,7 +516,7 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ return } log.Debug().Msg("provisioning data received") - jsonResponse(w, http.StatusOK, Response{ + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ Success: true, Status: "provisioning_data_received", }) @@ -591,7 +529,7 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ case <-time.After(45 * time.Second): log.Warn().Msg("Timeout waiting for provisioning response (scan)") // Using 400 here to match the old bridge - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "Timeout waiting for QR code scan", ErrCode: "M_BAD_REQUEST", @@ -600,18 +538,13 @@ func (prov *ProvisioningAPI) LinkWaitForScan(w http.ResponseWriter, r *http.Requ } } -type LinkWaitForAccountRequest struct { - SessionID string `json:"session_id"` - DeviceName string `json:"device_name"` // TODO this seems to not be used anywhere -} - func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(provisioningUserKey).(*User) - var body LinkWaitForAccountRequest + var body legacyprovision.LinkWaitForAccountRequest err := json.NewDecoder(r.Body).Decode(&body) if err != nil { - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "Error decoding JSON body", ErrCode: "M_BAD_JSON", @@ -620,7 +553,7 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R } sessionID, err := strconv.Atoi(body.SessionID) if err != nil { - jsonResponse(w, http.StatusBadRequest, Error{ + legacyprovision.JSONResponse(w, http.StatusBadRequest, legacyprovision.Error{ Success: false, Error: "Error decoding session ID in JSON body", ErrCode: "M_BAD_JSON", @@ -647,7 +580,7 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R case resp := <-handle.channel: if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { log.Err(resp.Err).Msg("Error waiting for account") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: resp.Err.Error(), ErrCode: "M_INTERNAL", @@ -656,7 +589,7 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R } if resp.State != signalmeow.StateProvisioningPreKeysRegistered { log.Err(resp.Err).Stringer("state", resp.State).Msg("unexpected state") - jsonResponse(w, http.StatusInternalServerError, Error{ + legacyprovision.JSONResponse(w, http.StatusInternalServerError, legacyprovision.Error{ Success: false, Error: fmt.Sprintf("Unexpected state %s", resp.State.String()), ErrCode: "M_INTERNAL", @@ -665,7 +598,7 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R } log.Debug().Msg("prekeys registered") - jsonResponse(w, http.StatusOK, Response{ + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ Success: true, Status: "prekeys_registered", UUID: user.SignalID.String(), @@ -677,7 +610,7 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R return case <-time.After(30 * time.Second): log.Warn().Msg("Timeout waiting for provisioning response (account)") - jsonResponse(w, http.StatusGatewayTimeout, Error{ + legacyprovision.JSONResponse(w, http.StatusGatewayTimeout, legacyprovision.Error{ Success: false, Error: "Timeout waiting for provisioning response (account)", ErrCode: "M_TIMEOUT", @@ -700,7 +633,7 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) { // For now do nothing - we need this API to return 200 to be compatible with // the old Signal bridge, which needed a call to Logout before allowing LinkNew // to be called, but we don't actually want to logout, we want to allow a reconnect. - jsonResponse(w, http.StatusOK, Response{ + legacyprovision.JSONResponse(w, http.StatusOK, legacyprovision.Response{ Success: true, Status: "logged_out", })