mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
parent
717f2989a8
commit
1e22e62a9a
8 changed files with 238 additions and 35 deletions
|
@ -8,7 +8,7 @@ require github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
|||
|
||||
require (
|
||||
go.mau.fi/gomuks v0.4.0
|
||||
go.mau.fi/util v0.8.4
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -67,7 +67,7 @@ require (
|
|||
go.mau.fi/webp v0.2.0 // indirect
|
||||
go.mau.fi/zeroconfig v0.1.3 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
|
@ -79,7 +79,7 @@ require (
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
maunium.net/go/mautrix v0.23.0 // indirect
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 // indirect
|
||||
mvdan.cc/xurls/v2 v2.6.0 // indirect
|
||||
)
|
||||
|
||||
|
|
|
@ -166,8 +166,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw=
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||
|
@ -179,8 +179,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
|||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
|
@ -261,7 +261,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
|
||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg=
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91/go.mod h1:q2U2IRLSFpglDhIpSjd8TnCNVzBNrUJBD8pmYCGQiwc=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
|
|
6
go.mod
6
go.mod
|
@ -18,7 +18,7 @@ require (
|
|||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
go.mau.fi/util v0.8.4
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003
|
||||
go.mau.fi/webp v0.2.0
|
||||
go.mau.fi/zeroconfig v0.1.3
|
||||
golang.org/x/crypto v0.32.0
|
||||
|
@ -27,7 +27,7 @@ require (
|
|||
golang.org/x/text v0.21.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mauflag v1.0.0
|
||||
maunium.net/go/mautrix v0.23.0
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
)
|
||||
|
||||
|
@ -41,7 +41,7 @@ require (
|
|||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
|
12
go.sum
12
go.sum
|
@ -68,16 +68,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw=
|
||||
go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
|
@ -100,7 +100,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.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
|
||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg=
|
||||
maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91/go.mod h1:q2U2IRLSFpglDhIpSjd8TnCNVzBNrUJBD8pmYCGQiwc=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
|
|
110
pkg/gomuks/keys.go
Normal file
110
pkg/gomuks/keys.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
// 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,
|
||||
})
|
||||
}
|
|
@ -53,6 +53,9 @@ func (gmx *Gomuks) CreateAPIRouter() http.Handler {
|
|||
api.HandleFunc("GET /sso", gmx.HandleSSOComplete)
|
||||
api.HandleFunc("POST /sso", gmx.PrepareSSO)
|
||||
api.HandleFunc("GET /media/{server}/{media_id}", gmx.DownloadMedia)
|
||||
api.HandleFunc("POST /keys/export", gmx.ExportKeys)
|
||||
api.HandleFunc("POST /keys/export/{room_id}", gmx.ExportKeys)
|
||||
api.HandleFunc("POST /keys/import", gmx.ImportKeys)
|
||||
api.HandleFunc("GET /codeblock/{style}", gmx.GetCodeblockCSS)
|
||||
return exhttp.ApplyMiddleware(
|
||||
api,
|
||||
|
@ -239,28 +242,34 @@ func (gmx *Gomuks) Authenticate(w http.ResponseWriter, r *http.Request) {
|
|||
if err == nil && gmx.validateAuth(authCookie.Value, false) {
|
||||
hlog.FromRequest(r).Debug().Msg("Authentication successful with existing cookie")
|
||||
gmx.writeTokenCookie(w, false, jsonOutput)
|
||||
} else if username, password, ok := r.BasicAuth(); !ok {
|
||||
} else if found, correct := gmx.doBasicAuth(r); found && correct {
|
||||
hlog.FromRequest(r).Debug().Msg("Authentication successful with username and password")
|
||||
gmx.writeTokenCookie(w, true, jsonOutput)
|
||||
} else {
|
||||
if !found {
|
||||
hlog.FromRequest(r).Debug().Msg("Requesting credentials for auth request")
|
||||
} else {
|
||||
hlog.FromRequest(r).Debug().Msg("Authentication failed with username and password, re-requesting credentials")
|
||||
}
|
||||
if allowPrompt {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="gomuks web" charset="UTF-8"`)
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
func (gmx *Gomuks) doBasicAuth(r *http.Request) (found, correct bool) {
|
||||
var username, password string
|
||||
username, password, found = r.BasicAuth()
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
usernameHash := sha256.Sum256([]byte(username))
|
||||
expectedUsernameHash := sha256.Sum256([]byte(gmx.Config.Web.Username))
|
||||
usernameCorrect := hmac.Equal(usernameHash[:], expectedUsernameHash[:])
|
||||
passwordCorrect := bcrypt.CompareHashAndPassword([]byte(gmx.Config.Web.PasswordHash), []byte(password)) == nil
|
||||
if usernameCorrect && passwordCorrect {
|
||||
hlog.FromRequest(r).Debug().Msg("Authentication successful with username and password")
|
||||
gmx.writeTokenCookie(w, true, jsonOutput)
|
||||
} else {
|
||||
hlog.FromRequest(r).Debug().Msg("Authentication failed with username and password, re-requesting credentials")
|
||||
if allowPrompt {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="gomuks web" charset="UTF-8"`)
|
||||
}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
correct = passwordCorrect && usernameCorrect
|
||||
return
|
||||
}
|
||||
|
||||
func isImageFetch(header http.Header) bool {
|
||||
|
|
|
@ -113,6 +113,39 @@ div.settings-view {
|
|||
}
|
||||
}
|
||||
|
||||
> div.key-export {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
margin: 0 .5rem;
|
||||
max-width: 25rem;
|
||||
|
||||
button {
|
||||
padding: .5rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: .5rem;
|
||||
border-radius: .5rem;
|
||||
|
||||
&[type="file"] {
|
||||
padding: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
> div.export-buttons, > form.import-buttons {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
|
||||
> form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div.misc-buttons > button {
|
||||
padding: .5rem 1rem;
|
||||
display: block;
|
||||
|
@ -126,4 +159,9 @@ div.settings-view {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
> hr {
|
||||
width: 100%;
|
||||
opacity: .2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -284,6 +284,49 @@ const AppliedSettingsView = ({ room }: SettingsViewProps) => {
|
|||
</div>
|
||||
}
|
||||
|
||||
const KeyExportView = ({ room }: SettingsViewProps) => {
|
||||
const [passphrase, setPassphrase] = useState("")
|
||||
const [hasFile, setHasFile] = useState(false)
|
||||
return <div className="key-export">
|
||||
<h3>Key export/import</h3>
|
||||
<input
|
||||
className="passphrase"
|
||||
type="password"
|
||||
value={passphrase}
|
||||
onChange={evt => setPassphrase(evt.target.value)}
|
||||
placeholder="Passphrase"
|
||||
/>
|
||||
<form
|
||||
className="import-buttons"
|
||||
action="_gomuks/keys/import"
|
||||
encType="multipart/form-data"
|
||||
method="post"
|
||||
target="_blank"
|
||||
>
|
||||
<input type="password" name="passphrase" hidden value={passphrase} />
|
||||
<input
|
||||
className="import-file"
|
||||
type="file"
|
||||
accept="text/plain"
|
||||
name="export"
|
||||
defaultValue=""
|
||||
onChange={evt => setHasFile(!!evt.target.files?.length)}
|
||||
/>
|
||||
<button type="submit" disabled={passphrase == "" || !hasFile}>Import keys</button>
|
||||
</form>
|
||||
<div className="export-buttons">
|
||||
<form action="_gomuks/keys/export" method="post" target="_blank">
|
||||
<input type="password" name="passphrase" hidden value={passphrase} />
|
||||
<button type="submit" disabled={passphrase == ""}>Export all keys</button>
|
||||
</form>
|
||||
<form action={`_gomuks/keys/export/${encodeURIComponent(room.roomID)}`} method="post" target="_blank">
|
||||
<input type="password" name="passphrase" hidden value={passphrase} />
|
||||
<button type="submit" disabled={passphrase == ""}>Export room keys</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const SettingsView = ({ room }: SettingsViewProps) => {
|
||||
const roomMeta = useEventAsState(room.meta)
|
||||
const client = use(ClientContext)!
|
||||
|
@ -393,6 +436,9 @@ const SettingsView = ({ room }: SettingsViewProps) => {
|
|||
</table>
|
||||
<CustomCSSInput setPref={setPref} room={room} />
|
||||
<AppliedSettingsView room={room} />
|
||||
<hr/>
|
||||
<KeyExportView room={room} />
|
||||
<hr/>
|
||||
<div className="misc-buttons">
|
||||
<button onClick={onClickOpenCSSApp}>Sign into css.gomuks.app</button>
|
||||
{window.Notification && !window.gomuksAndroid && <button onClick={client.requestNotificationPermission}>
|
||||
|
|
Loading…
Add table
Reference in a new issue