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 (
|
require (
|
||||||
go.mau.fi/gomuks v0.4.0
|
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 (
|
require (
|
||||||
|
@ -67,7 +67,7 @@ require (
|
||||||
go.mau.fi/webp v0.2.0 // indirect
|
go.mau.fi/webp v0.2.0 // indirect
|
||||||
go.mau.fi/zeroconfig v0.1.3 // indirect
|
go.mau.fi/zeroconfig v0.1.3 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // 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/image v0.23.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/net v0.34.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/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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
|
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.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 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
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.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw=
|
||||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
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 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
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 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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
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-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
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.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 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg=
|
||||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
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 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
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/gjson v1.18.0
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/yuin/goldmark v1.7.8
|
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/webp v0.2.0
|
||||||
go.mau.fi/zeroconfig v0.1.3
|
go.mau.fi/zeroconfig v0.1.3
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.32.0
|
||||||
|
@ -27,7 +27,7 @@ require (
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mauflag v1.0.0
|
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
|
mvdan.cc/xurls/v2 v2.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ require (
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.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
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // 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/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 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
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.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw=
|
||||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
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 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
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 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
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 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
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-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
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.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 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
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=
|
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 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
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.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg=
|
||||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
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 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
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("GET /sso", gmx.HandleSSOComplete)
|
||||||
api.HandleFunc("POST /sso", gmx.PrepareSSO)
|
api.HandleFunc("POST /sso", gmx.PrepareSSO)
|
||||||
api.HandleFunc("GET /media/{server}/{media_id}", gmx.DownloadMedia)
|
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)
|
api.HandleFunc("GET /codeblock/{style}", gmx.GetCodeblockCSS)
|
||||||
return exhttp.ApplyMiddleware(
|
return exhttp.ApplyMiddleware(
|
||||||
api,
|
api,
|
||||||
|
@ -239,28 +242,34 @@ func (gmx *Gomuks) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||||
if err == nil && gmx.validateAuth(authCookie.Value, false) {
|
if err == nil && gmx.validateAuth(authCookie.Value, false) {
|
||||||
hlog.FromRequest(r).Debug().Msg("Authentication successful with existing cookie")
|
hlog.FromRequest(r).Debug().Msg("Authentication successful with existing cookie")
|
||||||
gmx.writeTokenCookie(w, false, jsonOutput)
|
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")
|
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 {
|
if allowPrompt {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="gomuks web" charset="UTF-8"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="gomuks web" charset="UTF-8"`)
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
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))
|
usernameHash := sha256.Sum256([]byte(username))
|
||||||
expectedUsernameHash := sha256.Sum256([]byte(gmx.Config.Web.Username))
|
expectedUsernameHash := sha256.Sum256([]byte(gmx.Config.Web.Username))
|
||||||
usernameCorrect := hmac.Equal(usernameHash[:], expectedUsernameHash[:])
|
usernameCorrect := hmac.Equal(usernameHash[:], expectedUsernameHash[:])
|
||||||
passwordCorrect := bcrypt.CompareHashAndPassword([]byte(gmx.Config.Web.PasswordHash), []byte(password)) == nil
|
passwordCorrect := bcrypt.CompareHashAndPassword([]byte(gmx.Config.Web.PasswordHash), []byte(password)) == nil
|
||||||
if usernameCorrect && passwordCorrect {
|
correct = passwordCorrect && usernameCorrect
|
||||||
hlog.FromRequest(r).Debug().Msg("Authentication successful with username and password")
|
return
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isImageFetch(header http.Header) bool {
|
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 {
|
> div.misc-buttons > button {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -126,4 +159,9 @@ div.settings-view {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
width: 100%;
|
||||||
|
opacity: .2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,49 @@ const AppliedSettingsView = ({ room }: SettingsViewProps) => {
|
||||||
</div>
|
</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 SettingsView = ({ room }: SettingsViewProps) => {
|
||||||
const roomMeta = useEventAsState(room.meta)
|
const roomMeta = useEventAsState(room.meta)
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
|
@ -393,6 +436,9 @@ const SettingsView = ({ room }: SettingsViewProps) => {
|
||||||
</table>
|
</table>
|
||||||
<CustomCSSInput setPref={setPref} room={room} />
|
<CustomCSSInput setPref={setPref} room={room} />
|
||||||
<AppliedSettingsView room={room} />
|
<AppliedSettingsView room={room} />
|
||||||
|
<hr/>
|
||||||
|
<KeyExportView room={room} />
|
||||||
|
<hr/>
|
||||||
<div className="misc-buttons">
|
<div className="misc-buttons">
|
||||||
<button onClick={onClickOpenCSSApp}>Sign into css.gomuks.app</button>
|
<button onClick={onClickOpenCSSApp}>Sign into css.gomuks.app</button>
|
||||||
{window.Notification && !window.gomuksAndroid && <button onClick={client.requestNotificationPermission}>
|
{window.Notification && !window.gomuksAndroid && <button onClick={client.requestNotificationPermission}>
|
||||||
|
|
Loading…
Add table
Reference in a new issue