mirror of
https://github.com/Unkn0wnCat/matrix-veles.git
synced 2025-04-28 17:56:49 +02:00
Create auth API & comment code
This commit is contained in:
parent
1b15b12859
commit
c8d1c33cb4
17 changed files with 475 additions and 101 deletions
|
@ -58,6 +58,9 @@ func init() {
|
||||||
viper.SetDefault("bot.mongo.collection.entries", "entries")
|
viper.SetDefault("bot.mongo.collection.entries", "entries")
|
||||||
viper.SetDefault("bot.mongo.collection.lists", "lists")
|
viper.SetDefault("bot.mongo.collection.lists", "lists")
|
||||||
viper.SetDefault("bot.mongo.collection.rooms", "rooms")
|
viper.SetDefault("bot.mongo.collection.rooms", "rooms")
|
||||||
|
viper.SetDefault("bot.mongo.collection.users", "users")
|
||||||
|
viper.SetDefault("bot.web.listen", "127.0.0.1:8123")
|
||||||
|
viper.SetDefault("bot.web.secret", "hunter2")
|
||||||
|
|
||||||
cobra.OnInitialize(loadConfig)
|
cobra.OnInitialize(loadConfig)
|
||||||
}
|
}
|
||||||
|
@ -75,4 +78,8 @@ func loadConfig() {
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
log.Println("Using config file:", viper.ConfigFileUsed())
|
log.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viper.GetString("bot.web.secret") == "hunter2" {
|
||||||
|
log.Println("Web secret is not set! YOUR INSTALLATION IS INSECURE!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"github.com/Unkn0wnCat/matrix-veles/internal/bot"
|
"github.com/Unkn0wnCat/matrix-veles/internal/bot"
|
||||||
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
||||||
|
"github.com/Unkn0wnCat/matrix-veles/internal/web"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +34,7 @@ var runCmd = &cobra.Command{
|
||||||
The bot will log in to the homeserver and start posting updates to subscribed channels.`,
|
The bot will log in to the homeserver and start posting updates to subscribed channels.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
db.Connect()
|
db.Connect()
|
||||||
|
go web.StartServer()
|
||||||
bot.Run()
|
bot.Run()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -3,11 +3,12 @@ module github.com/Unkn0wnCat/matrix-veles
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7
|
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.3.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.10.1
|
||||||
go.mongodb.org/mongo-driver v1.8.3 // indirect
|
go.mongodb.org/mongo-driver v1.8.3
|
||||||
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||||
golang.org/x/text v0.3.7
|
|
||||||
maunium.net/go/mautrix v0.9.14
|
maunium.net/go/mautrix v0.9.14
|
||||||
)
|
)
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -133,6 +133,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
@ -167,8 +169,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 h1:oKYOfNR7Hp6XpZ4JqolL5u642Js5Z0n7psPVl+S5heo=
|
|
||||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
@ -182,6 +182,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
@ -208,6 +209,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
@ -372,6 +374,7 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
||||||
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
@ -404,7 +407,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
|
||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
@ -671,6 +673,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
|
|
@ -158,19 +158,20 @@ func handleMessageEvent(matrixClient *mautrix.Client, startTs int64) mautrix.Eve
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.URL != "" {
|
if content.URL != "" {
|
||||||
handleHashing(content, evt, matrixClient)
|
// This has an attachment!
|
||||||
|
handleHashing(content, evt, matrixClient) // -> handleHashing.go
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No attachment, is this a command?
|
||||||
if !strings.HasPrefix(content.Body, "!"+username) &&
|
if !strings.HasPrefix(content.Body, "!"+username) &&
|
||||||
!strings.HasPrefix(content.Body, "@"+username) &&
|
!strings.HasPrefix(content.Body, "@"+username) &&
|
||||||
!(strings.HasPrefix(content.Body, username) && strings.HasPrefix(content.FormattedBody, "<a href=\"https://matrix.to/#/"+matrixClient.UserID.String()+"\">")) {
|
!(strings.HasPrefix(content.Body, username) && strings.HasPrefix(content.FormattedBody, "<a href=\"https://matrix.to/#/"+matrixClient.UserID.String()+"\">")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient)
|
// It is a command!
|
||||||
|
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient) // -> commandParser.go
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,19 @@ import (
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StateEventPL struct {
|
type StateEventPowerLevel struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Sender string `json:"sender"`
|
Sender string `json:"sender"`
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
EventID string `json:"event_id"`
|
EventID string `json:"event_id"`
|
||||||
OriginServerTS int64 `json:"origin_server_ts"`
|
OriginServerTS int64 `json:"origin_server_ts"`
|
||||||
Content StateEventPLContent `json:"content"`
|
Content StateEventPowerLevelContent `json:"content"`
|
||||||
Unsigned struct {
|
Unsigned struct {
|
||||||
Age int `json:"age"`
|
Age int `json:"age"`
|
||||||
} `json:"unsigned"`
|
} `json:"unsigned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateEventPLContent struct {
|
type StateEventPowerLevelContent struct {
|
||||||
Ban int `json:"ban"`
|
Ban int `json:"ban"`
|
||||||
Events map[string]int `json:"events"`
|
Events map[string]int `json:"events"`
|
||||||
EventsDefault int `json:"events_default"`
|
EventsDefault int `json:"events_default"`
|
||||||
|
@ -33,45 +33,51 @@ type StateEventPLContent struct {
|
||||||
UsersDefault int `json:"users_default"`
|
UsersDefault int `json:"users_default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRoomState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPLContent, error) {
|
// GetRoomPowerLevelState returns the rooms current power levels from the state
|
||||||
|
func GetRoomPowerLevelState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPowerLevelContent, error) {
|
||||||
|
// https://matrix.example.com/_matrix/client/r0/rooms/<roomId.String()>/state
|
||||||
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
|
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
|
||||||
|
|
||||||
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
|
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
|
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateEvents []StateEventPL
|
// res contains an array of state events
|
||||||
|
var stateEvents []StateEventPowerLevel
|
||||||
|
|
||||||
err = json.Unmarshal(res, &stateEvents)
|
err = json.Unmarshal(res, &stateEvents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
|
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var plEventContent StateEventPLContent
|
// plEventContent will hold the final event
|
||||||
|
var plEventContent StateEventPowerLevelContent
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, e2 := range stateEvents {
|
for _, e2 := range stateEvents {
|
||||||
if e2.Type != event.StatePowerLevels.Type {
|
if e2.Type != event.StatePowerLevels.Type {
|
||||||
continue
|
continue // If the current event is not of the power level, skip.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is what we're looking for!
|
||||||
found = true
|
found = true
|
||||||
plEventContent = e2.Content
|
plEventContent = e2.Content
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
|
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following handle cases in which empty lists may have been parsed as nil
|
||||||
if plEventContent.Events == nil {
|
if plEventContent.Events == nil {
|
||||||
plEventContent.Events = make(map[string]int)
|
plEventContent.Events = make(map[string]int)
|
||||||
}
|
}
|
||||||
|
|
||||||
if plEventContent.Notifications == nil {
|
if plEventContent.Notifications == nil {
|
||||||
plEventContent.Notifications = make(map[string]int)
|
plEventContent.Notifications = make(map[string]int)
|
||||||
}
|
}
|
||||||
|
|
||||||
if plEventContent.Users == nil {
|
if plEventContent.Users == nil {
|
||||||
plEventContent.Users = make(map[string]int)
|
plEventContent.Users = make(map[string]int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// handleHashing hashes and checks a message, taking configured actions on match
|
||||||
func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixClient *mautrix.Client) {
|
func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixClient *mautrix.Client) {
|
||||||
url, err := content.URL.Parse()
|
url, err := content.URL.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,9 +31,7 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(reader io.ReadCloser) {
|
defer func(reader io.ReadCloser) { _ = reader.Close() }(reader)
|
||||||
_ = reader.Close()
|
|
||||||
}(reader)
|
|
||||||
|
|
||||||
hashWriter := sha512.New()
|
hashWriter := sha512.New()
|
||||||
if _, err = io.Copy(hashWriter, reader); err != nil {
|
if _, err = io.Copy(hashWriter, reader); err != nil {
|
||||||
|
@ -42,52 +41,51 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
||||||
|
|
||||||
sum := hex.EncodeToString(hashWriter.Sum(nil))
|
sum := hex.EncodeToString(hashWriter.Sum(nil))
|
||||||
|
|
||||||
|
// Fetch room configuration for adjusting behaviour
|
||||||
roomConfig := config.GetRoomConfig(evt.RoomID.String())
|
roomConfig := config.GetRoomConfig(evt.RoomID.String())
|
||||||
|
|
||||||
hashObj, err := db.GetEntryByHash(sum)
|
hashObj, err := db.GetEntryByHash(sum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||||
/*entry := model.DBEntry{
|
|
||||||
ID: primitive.NewObjectID(),
|
|
||||||
HashValue: sum,
|
|
||||||
FileURL: "placeholder",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
AddedBy: nil,
|
|
||||||
Comments: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
db.SaveEntry(&entry)*/
|
|
||||||
|
|
||||||
if roomConfig.Debug {
|
if roomConfig.Debug {
|
||||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG - This file is not on the hashlist: %s", sum))
|
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG - This file is not on the hashlist: %s", sum))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if roomConfig.Debug {
|
if roomConfig.Debug {
|
||||||
matrixClient.SendNotice(evt.RoomID, "DEBUG - Failed to check file. See log.")
|
matrixClient.SendNotice(evt.RoomID, "DEBUG - Failed to check file. See log.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Error trying to check database: %v", err)
|
fmt.Printf("Error trying to check database: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if roomConfig.Debug {
|
if roomConfig.Debug {
|
||||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG !!! This file is on the hashlist: %s", sum))
|
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG !!! This file is on the hashlist: %s", sum))
|
||||||
|
|
||||||
jsonVal, _ := json.Marshal(hashObj)
|
jsonVal, _ := json.Marshal(hashObj)
|
||||||
|
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", makeFancyJSON(jsonVal)))
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
json.Indent(&buf, jsonVal, "", " ")
|
|
||||||
|
|
||||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", buf.String()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !checkSubscription(&roomConfig, hashObj) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
|
||||||
|
|
||||||
|
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeFancyJSON formats / indents a JSON string
|
||||||
|
func makeFancyJSON(input []byte) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
json.Indent(&buf, input, "", " ")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSubscription checks if the room is subscribed to one of hashObjs lists
|
||||||
|
func checkSubscription(roomConfig *config.RoomConfig, hashObj *model.DBEntry) bool {
|
||||||
if roomConfig.HashChecker.SubscribedLists == nil {
|
if roomConfig.HashChecker.SubscribedLists == nil {
|
||||||
log.Printf("Room %s is not subscribed to any lists!", roomConfig.RoomID)
|
log.Printf("Room %s is not subscribed to any lists!", roomConfig.RoomID)
|
||||||
return // Not subscribed to any lists
|
return false // Not subscribed to any lists
|
||||||
}
|
}
|
||||||
|
|
||||||
subMap := make(map[string]bool)
|
subMap := make(map[string]bool)
|
||||||
|
@ -110,14 +108,42 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
log.Printf("Room %s is not subscribed to any lists of hashobj %s!", roomConfig.RoomID, hashObj.ID.Hex())
|
log.Printf("Room %s is not subscribed to any lists of hashobj %s!", roomConfig.RoomID, hashObj.ID.Hex())
|
||||||
return // Not subscribed
|
return false // Not subscribed
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
|
return true
|
||||||
|
|
||||||
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleIllegalContent is called when a hash-match is found to take configured actions
|
||||||
|
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
||||||
|
switch roomConfig.HashChecker.HashCheckMode {
|
||||||
|
case 0:
|
||||||
|
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
redactMessage(evt, matrixClient, hashObj)
|
||||||
|
if roomConfig.HashChecker.NoticeToChat {
|
||||||
|
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
muteUser(evt, matrixClient)
|
||||||
|
redactMessage(evt, matrixClient, hashObj)
|
||||||
|
if roomConfig.HashChecker.NoticeToChat {
|
||||||
|
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
banUser(evt, matrixClient, hashObj)
|
||||||
|
redactMessage(evt, matrixClient, hashObj)
|
||||||
|
if roomConfig.HashChecker.NoticeToChat {
|
||||||
|
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redactMessage deletes the message sent in the given event
|
||||||
func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
||||||
opts := mautrix.ReqRedact{Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex())}
|
opts := mautrix.ReqRedact{Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex())}
|
||||||
_, err := matrixClient.RedactEvent(evt.RoomID, evt.ID, opts)
|
_, err := matrixClient.RedactEvent(evt.RoomID, evt.ID, opts)
|
||||||
|
@ -126,8 +152,9 @@ func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
// muteUser sets a users power-level to -1 to prevent them from sending messages
|
||||||
plEventContent, err := GetRoomState(matrixClient, evt.RoomID)
|
func muteUser(evt *event.Event, matrixClient *mautrix.Client) {
|
||||||
|
plEventContent, err := GetRoomPowerLevelState(matrixClient, evt.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR: Could mute user - %v", err)
|
log.Printf("ERROR: Could mute user - %v", err)
|
||||||
return
|
return
|
||||||
|
@ -140,9 +167,9 @@ func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBE
|
||||||
log.Printf("ERROR: Could mute user - %v", err)
|
log.Printf("ERROR: Could mute user - %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// banUser bans the sender of an event from the room
|
||||||
func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
||||||
req := mautrix.ReqBanUser{
|
req := mautrix.ReqBanUser{
|
||||||
Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex()),
|
Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex()),
|
||||||
|
@ -152,6 +179,7 @@ func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEn
|
||||||
matrixClient.BanUser(evt.RoomID, &req)
|
matrixClient.BanUser(evt.RoomID, &req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postNotice posts a notice about the given event into its room
|
||||||
func postNotice(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
func postNotice(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
||||||
local, server, err := evt.Sender.Parse()
|
local, server, err := evt.Sender.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -172,32 +200,3 @@ If you believe this action was an accident, please contact an room administrator
|
||||||
SendAlert(matrixClient, evt.RoomID.String(), fmt.Sprintf(
|
SendAlert(matrixClient, evt.RoomID.String(), fmt.Sprintf(
|
||||||
`Veles Triggered: The message by %s (on %s) was flagged for containing material used by spammers or trolls! (Reference: %s)`, local, server, hashObj.ID.Hex()))
|
`Veles Triggered: The message by %s (on %s) was flagged for containing material used by spammers or trolls! (Reference: %s)`, local, server, hashObj.ID.Hex()))
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
|
||||||
switch roomConfig.HashChecker.HashCheckMode {
|
|
||||||
case 0:
|
|
||||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
redactMessage(evt, matrixClient, hashObj)
|
|
||||||
if roomConfig.HashChecker.NoticeToChat {
|
|
||||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
muteUser(evt, matrixClient, hashObj)
|
|
||||||
redactMessage(evt, matrixClient, hashObj)
|
|
||||||
if roomConfig.HashChecker.NoticeToChat {
|
|
||||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
banUser(evt, matrixClient, hashObj)
|
|
||||||
redactMessage(evt, matrixClient, hashObj)
|
|
||||||
if roomConfig.HashChecker.NoticeToChat {
|
|
||||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ package bot
|
||||||
}
|
}
|
||||||
|
|
||||||
if roomConfig.AlertChannel == nil {
|
if roomConfig.AlertChannel == nil {
|
||||||
roomPLState, err := GetRoomState(matrixClient, id.RoomID(room))
|
roomPLState, err := GetRoomPowerLevelState(matrixClient, id.RoomID(room))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to get room power levels - %v", err)
|
log.Printf("Failed to get room power levels - %v", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -35,16 +35,21 @@ type RoomConfig struct {
|
||||||
// Debug specifies if the bot shall run in dry run mode
|
// Debug specifies if the bot shall run in dry run mode
|
||||||
Debug bool `yaml:"debug" bson:"debug"`
|
Debug bool `yaml:"debug" bson:"debug"`
|
||||||
|
|
||||||
|
// AlertChannel is currently unused
|
||||||
AlertChannel *string `bson:"alert_channel"`
|
AlertChannel *string `bson:"alert_channel"`
|
||||||
|
|
||||||
|
// AdminPowerLevel specifies the power-level a user has to have to manage the room
|
||||||
AdminPowerLevel int `bson:"admin_power_level"`
|
AdminPowerLevel int `bson:"admin_power_level"`
|
||||||
|
|
||||||
|
// HashChecker contains configuration specific to the hash-checker
|
||||||
HashChecker HashCheckerConfig `bson:"hash_checker"`
|
HashChecker HashCheckerConfig `bson:"hash_checker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HashCheckerConfig struct {
|
type HashCheckerConfig struct {
|
||||||
|
// NoticeToChat specifies weather or not to post a public notice to chat
|
||||||
NoticeToChat bool `bson:"chat_notice"`
|
NoticeToChat bool `bson:"chat_notice"`
|
||||||
|
|
||||||
|
// NotificationPowerLevel is currently unused
|
||||||
NotificationPowerLevel int `yaml:"notification_level" bson:"notification_level"`
|
NotificationPowerLevel int `yaml:"notification_level" bson:"notification_level"`
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -58,5 +63,6 @@ type HashCheckerConfig struct {
|
||||||
*/
|
*/
|
||||||
HashCheckMode uint8 `yaml:"mode" bson:"hash_check_mode"`
|
HashCheckMode uint8 `yaml:"mode" bson:"hash_check_mode"`
|
||||||
|
|
||||||
|
// SubscribedLists contains the lists this room is subscribed to
|
||||||
SubscribedLists []*primitive.ObjectID `bson:"subscribed_lists" json:"subscribed_lists"`
|
SubscribedLists []*primitive.ObjectID `bson:"subscribed_lists" json:"subscribed_lists"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,3 +108,51 @@ func GetListByID(id primitive.ObjectID) (*model.DBHashList, error) {
|
||||||
|
|
||||||
return &object, nil
|
return &object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SaveUser(user *model.DBUser) error {
|
||||||
|
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||||
|
|
||||||
|
opts := options.Replace().SetUpsert(true)
|
||||||
|
|
||||||
|
filter := bson.D{{"_id", user.ID}}
|
||||||
|
|
||||||
|
_, err := db.Collection(viper.GetString("bot.mongo.collection.users")).ReplaceOne(context.TODO(), filter, user, opts)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByID(id primitive.ObjectID) (*model.DBUser, error) {
|
||||||
|
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||||
|
|
||||||
|
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"_id", id}})
|
||||||
|
if res.Err() != nil {
|
||||||
|
return nil, res.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
object := model.DBUser{}
|
||||||
|
|
||||||
|
err := res.Decode(&object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByUsername(username string) (*model.DBUser, error) {
|
||||||
|
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||||
|
|
||||||
|
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"username", username}})
|
||||||
|
if res.Err() != nil {
|
||||||
|
return nil, res.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
object := model.DBUser{}
|
||||||
|
|
||||||
|
err := res.Decode(&object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &object, nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ package model
|
||||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
type DBComment struct {
|
type DBComment struct {
|
||||||
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"`
|
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"` // CommentedBy contains a reference to the user who commented
|
||||||
Content string `bson:"content" json:"content"`
|
Content string `bson:"content" json:"content"` // Content is the body of the comment
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
|
|
||||||
type DBEntry struct {
|
type DBEntry struct {
|
||||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||||
Tags []string `bson:"tags" json:"tags"`
|
Tags []string `bson:"tags" json:"tags"` // Tags used for searching entries and ordering
|
||||||
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"`
|
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"` // PartOf specifies the lists this entry is part of
|
||||||
HashValue string `bson:"hash_value" json:"hash"`
|
HashValue string `bson:"hash_value" json:"hash"` // HashValue is the SHA512-hash of the file
|
||||||
FileURL string `bson:"file_url" json:"file_url"`
|
FileURL string `bson:"file_url" json:"file_url"` // FileURL may be set to a file link
|
||||||
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
|
Timestamp time.Time `bson:"timestamp" json:"timestamp"` // Timestamp of when this entry was added
|
||||||
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"`
|
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"` // AddedBy is a reference to the user who added this
|
||||||
Comments []*DBComment `bson:"comments" json:"comments"`
|
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this entry
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
type DBHashList struct {
|
type DBHashList struct {
|
||||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||||
Tags []string `bson:"tags" json:"tags"`
|
Tags []string `bson:"tags" json:"tags"` // Tags of this list for discovery, and sorting
|
||||||
Comments []*DBComment `bson:"comments" json:"comments"`
|
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this list
|
||||||
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"`
|
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"` // Maintainers contains references to the users who may edit this list
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,20 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBUser struct {
|
type DBUser struct {
|
||||||
Username string `bson:"username" json:"username"`
|
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||||
HashedPassword string `bson:"password" json:"password"`
|
|
||||||
|
|
||||||
Password *string `bson:"-" json:"-"`
|
Username string `bson:"username" json:"username"` // Username is the username the user has
|
||||||
|
HashedPassword string `bson:"password" json:"password"` // HashedPassword contains the bcrypt-ed password
|
||||||
|
|
||||||
|
MatrixLinks []*string `bson:"matrix_links" json:"matrix_links"` // MatrixLinks is the matrix-users this user has verified ownership over
|
||||||
|
PendingMatrixLinks []*string `bson:"pending_matrix_links" json:"pending_matrix_links"` // PendingMatrixLinks is the matrix-users pending verification
|
||||||
|
|
||||||
|
Password *string `bson:"-" json:"-"` // Password may never be sent out!
|
||||||
}
|
}
|
||||||
|
|
||||||
func (usr *DBUser) HashPassword() error {
|
func (usr *DBUser) HashPassword() error {
|
||||||
|
@ -23,6 +29,7 @@ func (usr *DBUser) HashPassword() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
usr.HashedPassword = base64.StdEncoding.EncodeToString(hash)
|
usr.HashedPassword = base64.StdEncoding.EncodeToString(hash)
|
||||||
|
usr.Password = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
86
internal/web/api/api.go
Normal file
86
internal/web/api/api.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupAPI(router *mux.Router) {
|
||||||
|
router.NotFoundHandler = NotFoundHandler{}
|
||||||
|
router.MethodNotAllowedHandler = MethodNotAllowedHandler{}
|
||||||
|
|
||||||
|
router.Path("/auth/login").Methods("POST").HandlerFunc(apiHandleAuthLogin)
|
||||||
|
router.Path("/auth/register").Methods("POST").HandlerFunc(apiHandleAuthRegister)
|
||||||
|
|
||||||
|
bot := router.PathPrefix("/bot").Subrouter()
|
||||||
|
bot.Use(checkAuthMiddleware)
|
||||||
|
|
||||||
|
bot.Path("/test").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(200)
|
||||||
|
|
||||||
|
claims := request.Context().Value("claims").(jwtClaims)
|
||||||
|
|
||||||
|
writer.Write([]byte(`hello ` + claims.Username))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
token := req.Header.Get("Authorization")
|
||||||
|
tokenSplit := strings.Split(token, " ")
|
||||||
|
|
||||||
|
if token == "" || len(tokenSplit) < 2 {
|
||||||
|
writeJSONError(res, http.StatusUnauthorized, errors.New("bearer token required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token = tokenSplit[1]
|
||||||
|
|
||||||
|
claims, _, err := parseToken(token)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(req.Context(), "claims", *claims)
|
||||||
|
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
next.ServeHTTP(res, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSONError(res http.ResponseWriter, statusCode int, err error) {
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
enc, _ := json.Marshal(struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
|
}{
|
||||||
|
Error: err.Error(),
|
||||||
|
ErrorCode: statusCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = res.Write(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotFoundHandler struct{}
|
||||||
|
|
||||||
|
func (NotFoundHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = res.Write([]byte(`{"error": "not_found","error_code":404}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
type MethodNotAllowedHandler struct{}
|
||||||
|
|
||||||
|
func (MethodNotAllowedHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
_, _ = res.Write([]byte(`{"error": "method_not_allowed","error_code":405}`))
|
||||||
|
}
|
173
internal/web/api/auth.go
Normal file
173
internal/web/api/auth.go
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
||||||
|
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiAuthRequestBody struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtClaims struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseToken(tokenString string) (*jwtClaims, *jwt.Token, error) {
|
||||||
|
claims := jwtClaims{}
|
||||||
|
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
|
||||||
|
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return jwtSigningKey, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &claims, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiHandleAuthLogin(res http.ResponseWriter, req *http.Request) {
|
||||||
|
body := req.Body
|
||||||
|
|
||||||
|
bodyContent := apiAuthRequestBody{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(body).Decode(&bodyContent)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.GetUserByUsername(bodyContent.Username)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||||
|
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = user.CheckPassword(bodyContent.Password)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
|
||||||
|
|
||||||
|
claims := jwtClaims{
|
||||||
|
Username: user.Username,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 365 * 100)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "veles-api",
|
||||||
|
Subject: user.ID.Hex(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
ss, err := token.SignedString(jwtSigningKey)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
enc, err := json.Marshal(struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}{
|
||||||
|
Token: ss,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = res.Write(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiAuthRegisterBody struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiHandleAuthRegister(res http.ResponseWriter, req *http.Request) {
|
||||||
|
body := req.Body
|
||||||
|
|
||||||
|
bodyContent := apiAuthRegisterBody{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(body).Decode(&bodyContent)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetUserByUsername(bodyContent.Username)
|
||||||
|
if err == nil {
|
||||||
|
writeJSONError(res, http.StatusBadRequest, errors.New("username taken"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.DBUser{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Username: bodyContent.Username,
|
||||||
|
Password: &bodyContent.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = user.HashPassword()
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to hash password"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.SaveUser(&user)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtSigningKey := viper.GetString("bot.web.secret")
|
||||||
|
|
||||||
|
claims := jwtClaims{
|
||||||
|
Username: user.Username,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "veles-api",
|
||||||
|
Subject: user.ID.Hex(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
ss, err := token.SignedString(jwtSigningKey)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
enc, err := json.Marshal(struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}{
|
||||||
|
Token: ss,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = res.Write(enc)
|
||||||
|
}
|
35
internal/web/web.go
Normal file
35
internal/web/web.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Unkn0wnCat/matrix-veles/internal/web/api"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartServer() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
//r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
||||||
|
|
||||||
|
apiRouter := r.PathPrefix("/api").Subrouter()
|
||||||
|
api.SetupAPI(apiRouter)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: viper.GetString("bot.web.listen"),
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Now serving web-interface on http://%s", viper.GetString("bot.web.listen"))
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
func HomeHandler(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(200)
|
||||||
|
writer.Write([]byte("hello world"))
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue