1
0
Fork 0
forked from Mirrors/gomuks
nyxmuks/pkg/gomuks/keys.go
2025-02-04 00:40:08 +02:00

110 lines
3.8 KiB
Go

// gomuks - A Matrix client written in Go.
// Copyright (C) 2025 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 distributed 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 <https://www.gnu.org/licenses/>.
package gomuks
import (
"errors"
"fmt"
"io"
"mime"
"net/http"
"strconv"
"github.com/rs/zerolog/hlog"
"go.mau.fi/util/dbutil"
"go.mau.fi/util/exhttp"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/id"
)
func (gmx *Gomuks) ExportKeys(w http.ResponseWriter, r *http.Request) {
found, correct := gmx.doBasicAuth(r)
if !found || !correct {
hlog.FromRequest(r).Debug().Msg("Requesting credentials for key export request")
w.Header().Set("WWW-Authenticate", `Basic realm="gomuks web" charset="UTF-8"`)
w.WriteHeader(http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Cache-Control", "no-store")
err := r.ParseForm()
if err != nil {
hlog.FromRequest(r).Err(err).Msg("Failed to parse form")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("Failed to parse form data\n"))
return
}
roomID := id.RoomID(r.PathValue("room_id"))
var sessions dbutil.RowIter[*crypto.InboundGroupSession]
filename := "gomuks-keys.txt"
if roomID == "" {
sessions = gmx.Client.CryptoStore.GetAllGroupSessions(r.Context())
} else {
filename = fmt.Sprintf("gomuks-keys-%s.txt", roomID)
sessions = gmx.Client.CryptoStore.GetGroupSessionsForRoom(r.Context(), roomID)
}
export, err := crypto.ExportKeysIter(r.FormValue("passphrase"), sessions)
if errors.Is(err, crypto.ErrNoSessionsForExport) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("No keys found\n"))
return
} else if err != nil {
hlog.FromRequest(r).Err(err).Msg("Failed to export keys")
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Failed to export keys (see logs for more details)\n"))
return
}
w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
w.Header().Set("Content-Length", strconv.Itoa(len(export)))
w.WriteHeader(http.StatusOK)
_, _ = w.Write(export)
}
var badMultipartForm = mautrix.RespError{ErrCode: "FI.MAU.GOMUKS.BAD_FORM_DATA", Err: "Failed to parse form data", StatusCode: http.StatusBadRequest}
func (gmx *Gomuks) ImportKeys(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(5 * 1024 * 1024)
if err != nil {
badMultipartForm.Write(w)
return
}
export, _, err := r.FormFile("export")
if err != nil {
badMultipartForm.WithMessage("Failed to get export file from form: %w", err).Write(w)
return
}
exportData, err := io.ReadAll(export)
if err != nil {
badMultipartForm.WithMessage("Failed to read export file: %w", err).Write(w)
return
}
importedCount, totalCount, err := gmx.Client.Crypto.ImportKeys(r.Context(), r.FormValue("passphrase"), exportData)
if err != nil {
hlog.FromRequest(r).Err(err).Msg("Failed to import keys")
mautrix.MUnknown.WithMessage("Failed to import keys: %w", err).Write(w)
return
}
hlog.FromRequest(r).Info().
Int("imported_count", importedCount).
Int("total_count", totalCount).
Msg("Successfully imported keys")
exhttp.WriteJSONResponse(w, http.StatusOK, map[string]int{
"imported": importedCount,
"total": totalCount,
})
}