From 84815913c4a7d5025de4be45af8138fb4a52a983 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 24 Oct 2020 20:36:42 +0200 Subject: [PATCH 01/67] Added 3 new config items for enabling oauth functionality --- admin.go | 4 ++++ server.go | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/admin.go b/admin.go index a960071..1ed517d 100644 --- a/admin.go +++ b/admin.go @@ -389,6 +389,10 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { configValue{Key: ConfigMaxRemarksLength, Default: DefaultMaxRemarksLength, Type: ConfigInt}, configValue{Key: ConfigUnlimitedVotes, Default: DefaultUnlimitedVotes, Type: ConfigBool}, + + configValue{Key: ConfigTwitchOauthEnabled, Default: DefaultTwitchOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigDiscordOauthEnabled, Default: DefaultDiscordOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigPatreonOauthEnabled, Default: DefaultPatreonOauthEnabled, Type: ConfigBool}, }, TypeString: ConfigString, diff --git a/server.go b/server.go index c32a194..44fc188 100644 --- a/server.go +++ b/server.go @@ -37,6 +37,10 @@ const ( DefaultMaxDescriptionLength int = 1000 DefaultMaxLinkLength int = 500 // length of all links combined DefaultMaxRemarksLength int = 200 + + DefaultTwitchOauthEnabled bool = false + DefaultDiscordOauthEnabled bool = false + DefaultPatreonOauthEnabled bool = false ) // configuration keys @@ -60,6 +64,10 @@ const ( ConfigMaxDescriptionLength string = "MaxDescriptionLength" ConfigMaxLinkLength string = "MaxLinkLength" ConfigMaxRemarksLength string = "MaxRemarksLength" + + ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" + ConfigDiscordOauthEnabled string = "DiscordOauthEnabled" + ConfigPatreonOauthEnabled string = "PatreonOauthEnabled" ) type Options struct { From 74301b1230a4bf5282363d999870675042e69118 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 24 Oct 2020 21:28:53 +0200 Subject: [PATCH 02/67] Used the config values to display 3 buttons. These buttons will be used to execute the initial oauth requests. Depending on the conifgured setting only a selection or none of the buttons get displayed. --- templates.go | 11 +++++++++-- templates/plain-login.html | 20 ++++++++++++++++++++ user.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/templates.go b/templates.go index e04b8fa..cd4fd07 100644 --- a/templates.go +++ b/templates.go @@ -114,8 +114,15 @@ type dataMovieError struct { type dataLoginForm struct { dataPageBase - ErrorMessage string - Authed bool + ErrorMessage string + Authed bool + OAuth bool + TwitchOAuth bool + DiscordOAuth bool + PatreonOAuth bool + TwitchOAuthURL string + DiscordOAuthURL string + PatreonOAuthURL string } type dataAddMovie struct { diff --git a/templates/plain-login.html b/templates/plain-login.html index e204387..da6c445 100644 --- a/templates/plain-login.html +++ b/templates/plain-login.html @@ -20,6 +20,26 @@
Create Account
+ + {{if .OAuth}} +
+ {{if .TwitchOAuth}} +
+ +
+ {{end}} + {{if .DiscordOAuth}} +
+ +
+ {{end}} + {{if .PatreonOAuth}} +
+ +
+ {{end}} +
+ {{end}} {{end}} {{end}} diff --git a/user.go b/user.go index 52704e6..29ebe99 100644 --- a/user.go +++ b/user.go @@ -169,6 +169,44 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { data := dataLoginForm{} doRedirect := false + twitchAuth, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) + return + } + data.TwitchOAuth = twitchAuth + + if twitchAuth { + data.TwitchOAuthURL = "TwitchOAuthURL" + } + + discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) + return + } + data.DiscordOAuth = discordAuth + + if discordAuth { + data.DiscordOAuthURL = "DiscordOAuthURL" + } + + patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) + return + } + data.PatreonOAuth = patreonAuth + + if patreonAuth { + data.PatreonOAuthURL = "PatreonOAuthURL" + } + + data.OAuth = twitchAuth || discordAuth || patreonAuth + if r.Method == "POST" { // do login From fafa4eabec2c2e0a0adcbec0d9e6eb5402c93f9c Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 00:44:18 +0100 Subject: [PATCH 03/67] Some minor changes of the "buttons" --- templates/plain-login.html | 40 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/templates/plain-login.html b/templates/plain-login.html index da6c445..8778d02 100644 --- a/templates/plain-login.html +++ b/templates/plain-login.html @@ -1,4 +1,8 @@ -{{/* Simple login form for mods and admins */}} +{{/* + Simple login form for mods and admins + + ^ That is BS - This is the basic login template for ALL users +*/}} {{define "header"}}{{end}} @@ -20,26 +24,20 @@
Create Account
- - {{if .OAuth}} -
- {{if .TwitchOAuth}} -
- -
- {{end}} - {{if .DiscordOAuth}} -
- -
- {{end}} - {{if .PatreonOAuth}} -
- -
- {{end}} -
- {{end}} +{{if .OAuth}} +
+ {{if .TwitchOAuth}} + Login with Twitch + {{end}} + {{if .DiscordOAuth}} + Login with Discord + {{end}} + {{if .PatreonOAuth}} + Login with Patreon + {{end}} +
+{{end}} + {{end}} {{end}} From fefc6e8d43ece8d7dcb6537bfb736765c499d21d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:52:42 +0100 Subject: [PATCH 04/67] Added handlers for twitch oauth request and callback --- oauth.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 oauth.go diff --git a/oauth.go b/oauth.go new file mode 100644 index 0000000..f4349fc --- /dev/null +++ b/oauth.go @@ -0,0 +1,84 @@ +package moviepoll + +import ( + "net/http" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/twitch" +) + +var twitchOAuthConfig = &oauth2.Config{} +var discordOAuthConfig = &oauth2.Config{} +var patreonOAuthConfig = &oauth2.Config{} +var oauthStateString string +var openStates = []string{} + +func (s *Server) initOauth() error { + twitchOauthEnabled, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) + if err != nil { + return err + } + if twitchOauthEnabled { + twitchClientID, err := s.data.GetCfgString(ConfigTwitchOauthClientID, DefaultTwitchOauthClientID) + if err != nil { + return err + } + + twitchClientSecret, err := s.data.GetCfgString(ConfigTwitchOauthClientSecret, DefaultTwitchOauthClientSecret) + if err != nil { + return err + } + + twitchOAuthConfig = &oauth2.Config{ + RedirectURL: "http://localhost:8090/user/login", + ClientID: twitchClientID, + ClientSecret: twitchClientSecret, + Scopes: []string{}, + Endpoint: twitch.Endpoint, + } + } + // TODO cry in a corner and figure out how to do this stuff for discord and patreon + + return nil +} + +func (s *Server) handlerTwitchOAuth(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + oauthStateString := getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Request) { + state := r.FormValue("state") + + ok := false + for _, expectedState := range openStates { + if state == expectedState { + ok = true + } + } + if !ok { + s.l.Info("Invalid OAuth state: '%s'", state) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + code := r.FormValue("code") + token, err := twitchOAuthConfig.Exchange(oauth2.NoContext, code) + if err != nil { + s.l.Info("Code exchange failed with '%s'", err) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + s.l.Debug("Token: %s", token) +} + +func (s *Server) handlerDiscordOAuth() { + +} + +func (s *Server) handlerPatreonOAuth() { + +} From 4e029e3bd3a3f1d2ff86c43f22b60f6137bcef43 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:53:35 +0100 Subject: [PATCH 05/67] Added oauth.go to the Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ebbb391..95d8d38 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ SOURCES = \ templates.go \ user.go \ util.go \ + oauth.go \ votes.go .PHONY: all data fmt server From 597fb112001aabdd8f4519cd4316399481b691de Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:54:06 +0100 Subject: [PATCH 06/67] Added golang.org/x/oauth2 dependency --- go.mod | 1 + go.sum | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+) diff --git a/go.mod b/go.mod index 0bd99db..234ce6f 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,5 @@ require ( github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/rivo/uniseg v0.1.0 + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 ) diff --git a/go.sum b/go.sum index 8fb9047..dbe0317 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,370 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +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-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zorchenhimer/moviepolls v0.0.0-20191220220302-b92d292bcc8d h1:5lDzIViZdDSjMqrqHpPsAKznycH0gqfI/rsUGpEN0zI= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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-20191204190536-9bdfabe68543/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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From e52ada12a22d85aa183e3e4e78887fe2ff0277d5 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:54:45 +0100 Subject: [PATCH 07/67] Added several config fields for the oauth --- admin.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin.go b/admin.go index 1ed517d..e486524 100644 --- a/admin.go +++ b/admin.go @@ -391,8 +391,14 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { configValue{Key: ConfigUnlimitedVotes, Default: DefaultUnlimitedVotes, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthEnabled, Default: DefaultTwitchOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigTwitchOauthClientID, Default: DefaultTwitchOauthClientID, Type: ConfigString}, + configValue{Key: ConfigTwitchOauthClientSecret, Default: DefaultTwitchOauthClientSecret, Type: ConfigString}, configValue{Key: ConfigDiscordOauthEnabled, Default: DefaultDiscordOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigDiscordOauthClientID, Default: DefaultDiscordOauthClientID, Type: ConfigString}, + configValue{Key: ConfigDiscordOauthClientSecret, Default: DefaultDiscordOauthClientSecret, Type: ConfigString}, configValue{Key: ConfigPatreonOauthEnabled, Default: DefaultPatreonOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigPatreonOauthClientID, Default: DefaultPatreonOauthClientID, Type: ConfigString}, + configValue{Key: ConfigPatreonOauthClientSecret, Default: DefaultPatreonOauthClientSecret, Type: ConfigString}, }, TypeString: ConfigString, From e55d7d5a956d290d36ec4f379b742c956a89b582 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:55:51 +0100 Subject: [PATCH 08/67] Removed unnecessary url data fields --- templates.go | 15 ++++++--------- templates/plain-login.html | 6 +++--- user.go | 13 +------------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/templates.go b/templates.go index cd4fd07..2b36125 100644 --- a/templates.go +++ b/templates.go @@ -114,15 +114,12 @@ type dataMovieError struct { type dataLoginForm struct { dataPageBase - ErrorMessage string - Authed bool - OAuth bool - TwitchOAuth bool - DiscordOAuth bool - PatreonOAuth bool - TwitchOAuthURL string - DiscordOAuthURL string - PatreonOAuthURL string + ErrorMessage string + Authed bool + OAuth bool + TwitchOAuth bool + DiscordOAuth bool + PatreonOAuth bool } type dataAddMovie struct { diff --git a/templates/plain-login.html b/templates/plain-login.html index 8778d02..a1a2057 100644 --- a/templates/plain-login.html +++ b/templates/plain-login.html @@ -28,13 +28,13 @@ {{if .OAuth}}
{{if .TwitchOAuth}} - Login with Twitch + Login with Twitch {{end}} {{if .DiscordOAuth}} - Login with Discord + Login with Discord {{end}} {{if .PatreonOAuth}} - Login with Patreon + Login with Patreon {{end}}
{{end}} diff --git a/user.go b/user.go index 29ebe99..8e1c9c8 100644 --- a/user.go +++ b/user.go @@ -155,6 +155,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { } } func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() if err != nil { s.l.Error("Error parsing login form: %v", err) @@ -177,10 +178,6 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { } data.TwitchOAuth = twitchAuth - if twitchAuth { - data.TwitchOAuthURL = "TwitchOAuthURL" - } - discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) @@ -189,10 +186,6 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { } data.DiscordOAuth = discordAuth - if discordAuth { - data.DiscordOAuthURL = "DiscordOAuthURL" - } - patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) @@ -201,10 +194,6 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { } data.PatreonOAuth = patreonAuth - if patreonAuth { - data.PatreonOAuthURL = "PatreonOAuthURL" - } - data.OAuth = twitchAuth || discordAuth || patreonAuth if r.Method == "POST" { From bfac2fede2f8af35d096ac6606e4897220d015df Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:56:32 +0100 Subject: [PATCH 09/67] Fixed the callback url and return back to the login page --- oauth.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauth.go b/oauth.go index f4349fc..12ece14 100644 --- a/oauth.go +++ b/oauth.go @@ -30,7 +30,7 @@ func (s *Server) initOauth() error { } twitchOAuthConfig = &oauth2.Config{ - RedirectURL: "http://localhost:8090/user/login", + RedirectURL: "http://localhost:8090/user/login/twitch/callback", ClientID: twitchClientID, ClientSecret: twitchClientSecret, Scopes: []string{}, @@ -73,6 +73,8 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } s.l.Debug("Token: %s", token) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } func (s *Server) handlerDiscordOAuth() { From 7935a186995b9cb639238111d4b678cbe46e20d3 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 2 Nov 2020 02:57:12 +0100 Subject: [PATCH 10/67] Added config values and initiate Oauth in the server startup --- server.go | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/server.go b/server.go index 44fc188..bd8c7d7 100644 --- a/server.go +++ b/server.go @@ -38,9 +38,15 @@ const ( DefaultMaxLinkLength int = 500 // length of all links combined DefaultMaxRemarksLength int = 200 - DefaultTwitchOauthEnabled bool = false - DefaultDiscordOauthEnabled bool = false - DefaultPatreonOauthEnabled bool = false + DefaultTwitchOauthEnabled bool = false + DefaultTwitchOauthClientID string = "" + DefaultTwitchOauthClientSecret string = "" + DefaultDiscordOauthEnabled bool = false + DefaultDiscordOauthClientID string = "" + DefaultDiscordOauthClientSecret string = "" + DefaultPatreonOauthEnabled bool = false + DefaultPatreonOauthClientID string = "" + DefaultPatreonOauthClientSecret string = "" ) // configuration keys @@ -65,9 +71,15 @@ const ( ConfigMaxLinkLength string = "MaxLinkLength" ConfigMaxRemarksLength string = "MaxRemarksLength" - ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" - ConfigDiscordOauthEnabled string = "DiscordOauthEnabled" - ConfigPatreonOauthEnabled string = "PatreonOauthEnabled" + ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" + ConfigTwitchOauthClientID string = "TwitchOauthClientID" + ConfigTwitchOauthClientSecret string = "TwitchOauthSecret" + ConfigDiscordOauthEnabled string = "DiscordOauthEnabled" + ConfigDiscordOauthClientID string = "DiscordOauthClientID" + ConfigDiscordOauthClientSecret string = "DiscordOauthClientSecret" + ConfigPatreonOauthEnabled string = "PatreonOauthEnabled" + ConfigPatreonOauthClientID string = "PatreonOauthClientID" + ConfigPatreonOauthClientSecret string = "PatreonOauthClientSecret" ) type Options struct { @@ -177,6 +189,11 @@ func NewServer(options Options) (*Server, error) { fmt.Printf("Claim admin: %s/auth/%s Password: %s\n", host, urlKey.Url, urlKey.Key) } + err = server.initOauth() + if err != nil { + return nil, err + } + mux := http.NewServeMux() mux.Handle("/api/", apiHandler{}) mux.HandleFunc("/movie/", server.handlerMovie) @@ -189,6 +206,9 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user", server.handlerUser) mux.HandleFunc("/user/login", server.handlerUserLogin) + mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuth) + mux.HandleFunc("/user/login/twitch/callback", server.handlerTwitchOAuthCallback) + mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) From 02f222596068effafcde9541257a624f3d782bdb Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 4 Nov 2020 02:16:23 +0100 Subject: [PATCH 11/67] Added account creation for twitch oauth --- oauth.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/oauth.go b/oauth.go index 12ece14..237ad4b 100644 --- a/oauth.go +++ b/oauth.go @@ -1,8 +1,12 @@ package moviepoll import ( + "encoding/json" + "io/ioutil" "net/http" + "time" + "github.com/zorchenhimer/MoviePolls/common" "golang.org/x/oauth2" "golang.org/x/oauth2/twitch" ) @@ -33,7 +37,7 @@ func (s *Server) initOauth() error { RedirectURL: "http://localhost:8090/user/login/twitch/callback", ClientID: twitchClientID, ClientSecret: twitchClientSecret, - Scopes: []string{}, + Scopes: []string{"user:read:email"}, Endpoint: twitch.Endpoint, } } @@ -72,7 +76,56 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } - s.l.Debug("Token: %s", token) + + req, err := http.NewRequest("GET", "https://api.twitch.tv/helix/users", nil) + req.Header.Add("Authorization", "Bearer "+token.AccessToken) + req.Header.Add("Client-Id", twitchOAuthConfig.ClientID) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + + } + if resp.StatusCode != 200 { + s.l.Error("Status Code is not 200, its %v", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + s.l.Error(err.Error()) + } + + var data map[string][]map[string]interface{} + + if err := json.Unmarshal(body, &data); err != nil { + s.l.Error(err.Error()) + s.l.Debug("%v", data) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + newUser := &common.User{ + Name: data["data"][0]["display_name"].(string), + Password: token.AccessToken, + Email: data["data"][0]["email"].(string), + NotifyCycleEnd: false, + NotifyVoteSelection: false, + PassDate: time.Now(), + } + s.l.Debug("adding user: %v", newUser) + _, err = s.data.AddUser(newUser) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + s.l.Debug("logging in") + s.login(newUser, w, r) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } From 6b1cbf5cde742f6bbd05c9f608b068e074838d53 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 14 Nov 2020 20:24:35 +0100 Subject: [PATCH 12/67] Some comments --- oauth.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/oauth.go b/oauth.go index 237ad4b..beb896a 100644 --- a/oauth.go +++ b/oauth.go @@ -11,10 +11,12 @@ import ( "golang.org/x/oauth2/twitch" ) +// just some global variables var twitchOAuthConfig = &oauth2.Config{} var discordOAuthConfig = &oauth2.Config{} var patreonOAuthConfig = &oauth2.Config{} -var oauthStateString string + +// var oauthStateString string var openStates = []string{} func (s *Server) initOauth() error { @@ -48,8 +50,12 @@ func (s *Server) initOauth() error { func (s *Server) handlerTwitchOAuth(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list oauthStateString := getCryptRandKey(32) openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect url := twitchOAuthConfig.AuthCodeURL(oauthStateString) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } @@ -64,7 +70,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque } } if !ok { - s.l.Info("Invalid OAuth state: '%s'", state) + s.l.Info("Invalid/Unknown OAuth state string: '%s'", state) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -72,11 +78,12 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque code := r.FormValue("code") token, err := twitchOAuthConfig.Exchange(oauth2.NoContext, code) if err != nil { - s.l.Info("Code exchange failed with '%s'", err) + s.l.Info("Code exchange failed: %s", err) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } + // Request the User data from the API req, err := http.NewRequest("GET", "https://api.twitch.tv/helix/users", nil) req.Header.Add("Authorization", "Bearer "+token.AccessToken) req.Header.Add("Client-Id", twitchOAuthConfig.ClientID) @@ -84,7 +91,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque client := &http.Client{} resp, err := client.Do(req) if err != nil { - s.l.Error(err.Error()) + s.l.Info("Could not retrieve Userdata from Twitch API: %s", err) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return @@ -107,6 +114,11 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } + // TODO check if the user already exists + // looping through all users and checking the auth data? + // how to determine if a request is for a given user? + + // Create a new User if no matching user is found newUser := &common.User{ Name: data["data"][0]["display_name"].(string), Password: token.AccessToken, @@ -115,8 +127,10 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque NotifyVoteSelection: false, PassDate: time.Now(), } + s.l.Debug("adding user: %v", newUser) - _, err = s.data.AddUser(newUser) + newUser.Id, err = s.data.AddUser(newUser) + if err != nil { s.l.Error(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) From be23ffa619cc310b813c5e07f7107a8cd5e1d421 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:20:09 +0100 Subject: [PATCH 13/67] Added a new struct "AuthMethod" This struct is used to manage the different authentication/login methods for moviepolls. It will be used within the user struct and partake in 1 to 1 relations with the user struct. --- common/AuthMethod.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 common/AuthMethod.go diff --git a/common/AuthMethod.go b/common/AuthMethod.go new file mode 100644 index 0000000..fe58571 --- /dev/null +++ b/common/AuthMethod.go @@ -0,0 +1,22 @@ +package common + +import "time" + +type AuthType string + +const ( + DISCORD = "Discord" + TWITCH = "Twitch" + PATREON = "Patreon" + LOCAL = "Local" +) + +type AuthMethod struct { + Id int + Type AuthType + Password string + PassDate time.Time + AuthToken string + RefreshToken string + RefreshDate time.Time +} From 12d0851f54fdd4deb50d2af79642e39566cfc5fe Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:22:52 +0100 Subject: [PATCH 14/67] Adapted user.go to the AuthMethod struct --- common/user.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/common/user.go b/common/user.go index 1f5bedd..5630b45 100644 --- a/common/user.go +++ b/common/user.go @@ -14,17 +14,15 @@ const ( ) type User struct { - Id int - Name string - Password string - OAuthToken string - Email string // nil if user didn't opt-in. + Id int + Name string + Email string // nil if user didn't opt-in. NotifyCycleEnd bool NotifyVoteSelection bool Privilege PrivilegeLevel - PassDate time.Time + AuthMethods []*AuthMethod // Does this user ignore rate limit? (default true for mod/admin) RateLimitOverride bool @@ -52,13 +50,12 @@ func (u User) IsMod() bool { func (u User) String() string { return fmt.Sprintf( - "User{Id:%d Name:%q Email:%q NotifyCycleEnd:%t NotifyVoteSelection:%t Privilege:%d PassDate:%s}", + "User{Id:%d Name:%q Email:%q NotifyCycleEnd:%t NotifyVoteSelection:%t Privilege:%d}", u.Id, u.Name, u.Email, u.NotifyCycleEnd, u.NotifyVoteSelection, u.Privilege, - u.PassDate, ) } From 0c1251b9f6bf1250d17f5f802b3c533caa5c9397 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:59:01 +0100 Subject: [PATCH 15/67] Added some new Functions and cleaned up interface --- data/connector.go | 88 +++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/data/connector.go b/data/connector.go index a125729..0a6f777 100644 --- a/data/connector.go +++ b/data/connector.go @@ -29,25 +29,37 @@ func register(backend string, initFunc constructor) { } type DataConnector interface { - GetCurrentCycle() (*common.Cycle, error) // Return nil when no cycle is active. + + // ################## + // ##### CREATE ##### + // ################## + + // TODO: remove AddCycle() + AddCycle(plannedEnd *time.Time) (int, error) + AddOldCycle(cycle *common.Cycle) (int, error) + AddMovie(movie *common.Movie) (int, error) + AddUser(user *common.User) (int, error) + AddTag(tag *common.Tag) (int, error) + AddAuthMethod(authMethod *common.AuthMethod) (int, error) + AddLink(link *common.Link) (int, error) + AddVote(userId, movieId int) error + + // ###################### + // ##### READ (get) ##### + // ###################### GetCycle(id int) (*common.Cycle, error) + GetCurrentCycle() (*common.Cycle, error) // Return nil when no cycle is active. GetMovie(id int) (*common.Movie, error) - GetUser(id int) (*common.User, error) GetActiveMovies() ([]*common.Movie, error) - GetTag(id int) *common.Tag - GetLink(id int) *common.Link - - SearchMovieTitles(query string) ([]*common.Movie, error) - FindTag(name string) (int, error) - FindLink(url string) (int, error) - + GetUser(id int) (*common.User, error) + GetUsers(start, count int) ([]*common.User, error) GetUserVotes(userId int) ([]*common.Movie, error) GetUserMovies(userId int) ([]*common.Movie, error) - //GetMovieVotes(userId int) []*Movie - UserLogin(name, hashedPw string) (*common.User, error) - + GetTag(id int) *common.Tag + GetAuthMethod(id int) *common.AuthMethod + GetLink(id int) *common.Link // Return a list of past cycles. Start and end are an offset from // the current. Ie, a start of 0 and an end of 5 will get the last // finished cycle and the four preceding it. Currently active cycle will @@ -57,37 +69,53 @@ type DataConnector interface { // Get all the movies that belong to the given Cycle GetMoviesFromCycle(id int) ([]*common.Movie, error) - // TODO: remove AddCycle() - AddCycle(plannedEnd *time.Time) (int, error) - AddOldCycle(cycle *common.Cycle) (int, error) - AddMovie(movie *common.Movie) (int, error) - AddUser(user *common.User) (int, error) - AddTag(tag *common.Tag) (int, error) - AddLink(link *common.Link) (int, error) + // ####################### + // ##### READ (find) ##### + // ####################### + + FindTag(name string) (int, error) + FindLink(url string) (int, error) + + // ################## + // ##### UPDATE ##### + // ################## + + UpdateUser(user *common.User) error + UpdateMovie(movie *common.Movie) error + UpdateCycle(cycle *common.Cycle) error + UpdateAuthMethod(authMethod *common.AuthMethod) error + + // ################## + // ##### DELETE ##### + // ################## - AddVote(userId, movieId int) error DeleteVote(userId, movieId int) error DeleteTag(tagId int) + DeleteAuthMethod(authMethodId int) DeleteLink(linkId int) + RemoveMovie(movieId int) error + // Delete a user and their associated votes. Should this include votes for + // past cycles or just the current? (currently removes all) + PurgeUser(userId int) error // Removes votes older than age DecayVotes(age int) error - UpdateUser(user *common.User) error - UpdateMovie(movie *common.Movie) error - UpdateCycle(cycle *common.Cycle) error + // ################ + // ##### MISC ##### + // ################ + + UserLocalLogin(name, hashedPw string) (*common.User, error) + UserDiscordLogin(name, token string) (*common.User, error) + UserTwitchLogin(name, token string) (*common.User, error) + UserPatreonLogin(name, token string) (*common.User, error) + + SearchMovieTitles(query string) ([]*common.Movie, error) CheckMovieExists(title string) (bool, error) CheckUserExists(name string) (bool, error) UserVotedForMovie(userId, movieId int) (bool, error) - // Admin stuff - GetUsers(start, count int) ([]*common.User, error) - // Delete a user and their associated votes. Should this include votes for - // past cycles or just the current? (currently removes all) - PurgeUser(userId int) error - RemoveMovie(movieId int) error - // Configuration stuff // The default value must be passed in. If no key is found, the default // value *is not* saved here. From 927064e06ec509b5b700244f0a1f0d8d81a727a3 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:59:41 +0100 Subject: [PATCH 16/67] Implemented json specific code for AuthMethod usage Implemented many functions added previously. Changed user related functions to now use a "jsonUser" struct. --- data/json.go | 179 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 159 insertions(+), 20 deletions(-) diff --git a/data/json.go b/data/json.go index adf8254..e0a37bf 100644 --- a/data/json.go +++ b/data/json.go @@ -97,6 +97,46 @@ func (j *jsonConnector) newJsonMovie(movie *common.Movie) jsonMovie { return jm } +type jsonUser struct { + Id int + Name string + Email string + + NotifyCycleEnd bool + NotifyVoteSelection bool + Privilege int + + AuthMethods []int + + RateLimitOverride bool + LastMovieAdd *time.Time +} + +func (j *jsonConnector) newJsonUser(user *common.User) jsonUser { + authMethods := []int{} + if user.AuthMethods != nil { + for _, auth := range user.AuthMethods { + authMethods = append(authMethods, auth.Id) + } + } + + id := j.nextUserId() + + ju := jsonUser{ + Id: id, + Name: user.Name, + Email: user.Email, + NotifyCycleEnd: user.NotifyCycleEnd, + NotifyVoteSelection: user.NotifyVoteSelection, + Privilege: int(user.Privilege), + AuthMethods: authMethods, + RateLimitOverride: user.RateLimitOverride, + LastMovieAdd: &user.LastMovieAdd, + } + + return ju +} + type jsonVote struct { UserId int MovieId int @@ -137,12 +177,13 @@ type jsonConnector struct { filename string `json:"-"` lock *sync.RWMutex - Cycles map[int]jsonCycle - Movies map[int]jsonMovie - Users map[int]*common.User - Votes []jsonVote - Tags map[int]*common.Tag - Links map[int]*common.Link + Cycles map[int]jsonCycle + Movies map[int]jsonMovie + Users map[int]jsonUser + Votes []jsonVote + Tags map[int]*common.Tag + Links map[int]*common.Link + AuthMethods map[int]*common.AuthMethod //Settings Configurator Settings map[string]configValue @@ -169,7 +210,7 @@ func newJsonConnector(filename string, l *common.Logger) (*jsonConnector, error) Cycles: map[int]jsonCycle{}, Movies: map[int]jsonMovie{}, - Users: map[int]*common.User{}, + Users: map[int]jsonUser{}, Tags: map[int]*common.Tag{}, Links: map[int]*common.Link{}, l: l, @@ -199,7 +240,7 @@ func loadJson(filename string, l *common.Logger) (*jsonConnector, error) { } if data.Users == nil { - data.Users = make(map[int]*common.User) + data.Users = make(map[int]jsonUser) } if data.Votes == nil { @@ -537,6 +578,32 @@ func (j *jsonConnector) movieFromJson(jMovie jsonMovie) *common.Movie { return movie } +func (j *jsonConnector) userFromJson(jUser jsonUser) *common.User { + + authMethods := []*common.AuthMethod{} + + for _, id := range jUser.AuthMethods { + a, ok := j.AuthMethods[id] + if ok { + authMethods = append(authMethods, a) + } + } + + user := &common.User{ + Id: jUser.Id, + Name: jUser.Name, + Email: jUser.Email, + + NotifyCycleEnd: jUser.NotifyCycleEnd, + NotifyVoteSelection: jUser.NotifyVoteSelection, + AuthMethods: authMethods, + RateLimitOverride: jUser.RateLimitOverride, + LastMovieAdd: *jUser.LastMovieAdd, + } + + return user +} + func (j *jsonConnector) GetMoviesFromCycle(id int) ([]*common.Movie, error) { j.lock.RLock() defer j.lock.RUnlock() @@ -562,24 +629,38 @@ func (j *jsonConnector) GetMoviesFromCycle(id int) ([]*common.Movie, error) { } // UserLogin returns a user if the given username and password match a user. -func (j *jsonConnector) UserLogin(name, hashedPw string) (*common.User, error) { +func (j *jsonConnector) UserLocalLogin(name, hashedPw string) (*common.User, error) { j.lock.RLock() defer j.lock.RUnlock() - name = strings.ToLower(name) - for _, user := range j.Users { - if strings.ToLower(user.Name) == name { - if hashedPw == user.Password { + user := j.findUserByName(name) + + for _, auth := range user.AuthMethods { + if auth.Type == "Local" { + if hashedPw == auth.Password { return user, nil } j.l.Info("Bad password for user %s\n", name) return nil, fmt.Errorf("Invalid login credentials") } } + j.l.Info("User with name %s not found\n", name) return nil, fmt.Errorf("Invalid login credentials") } +func (j *jsonConnector) UserDiscordLogin(name, hashedPw string) (*common.User, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (j *jsonConnector) UserTwitchLogin(name, hashedPw string) (*common.User, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (j *jsonConnector) UserPatreonLogin(name, hashedPw string) (*common.User, error) { + return nil, fmt.Errorf("Not implemented") +} + // Get the total number of users func (j *jsonConnector) GetUserCount() int { j.lock.RLock() @@ -733,7 +814,7 @@ func (j *jsonConnector) AddUser(user *common.User) (int, error) { defer j.lock.Unlock() if j.Users == nil { - j.Users = map[int]*common.User{} + j.Users = map[int]jsonUser{} } if _, exists := j.Users[user.Id]; exists { @@ -747,10 +828,10 @@ func (j *jsonConnector) AddUser(user *common.User) (int, error) { } } - user.Id = j.nextUserId() + u := j.newJsonUser(user) + j.Users[u.Id] = u - j.Users[user.Id] = user - return user.Id, j.save() + return u.Id, j.save() } func (j *jsonConnector) AddVote(userId, movieId int) error { @@ -808,6 +889,18 @@ func (j *jsonConnector) AddTag(tag *common.Tag) (int, error) { return id, j.save() } +func (j *jsonConnector) AddAuthMethod(authMethod *common.AuthMethod) (int, error) { + j.lock.Lock() + defer j.lock.Unlock() + + id := j.nextAuthMethodId() + + authMethod.Id = id + + j.AuthMethods[id] = authMethod + return id, j.save() +} + func (j *jsonConnector) FindTag(name string) (int, error) { j.lock.RLock() defer j.lock.RUnlock() @@ -828,6 +921,25 @@ func (j *jsonConnector) GetTag(id int) *common.Tag { return j.Tags[id] } +func (j *jsonConnector) GetAuthMethod(id int) *common.AuthMethod { + j.lock.RLock() + defer j.lock.RUnlock() + return j.AuthMethods[id] +} + +func (j *jsonConnector) UpdateAuthMethod(authMethod *common.AuthMethod) error { + j.lock.RLock() + defer j.lock.RUnlock() + + _, ok := j.AuthMethods[authMethod.Id] + + if !ok { + return fmt.Errorf("No AuthMethod with Id %d found.", authMethod.Id) + } + + j.AuthMethods[authMethod.Id] = authMethod + return nil +} func (j *jsonConnector) DeleteTag(id int) { j.lock.Lock() defer j.lock.Unlock() @@ -835,6 +947,13 @@ func (j *jsonConnector) DeleteTag(id int) { delete(j.Tags, id) } +func (j *jsonConnector) DeleteAuthMethod(id int) { + j.lock.Lock() + defer j.lock.Unlock() + + delete(j.AuthMethods, id) +} + func (j *jsonConnector) nextTagId() int { highest := 0 for _, t := range j.Tags { @@ -845,6 +964,16 @@ func (j *jsonConnector) nextTagId() int { return highest + 1 } +func (j *jsonConnector) nextAuthMethodId() int { + highest := 0 + for _, a := range j.AuthMethods { + if a.Id >= highest { + highest = a.Id + } + } + return highest + 1 +} + func (j *jsonConnector) AddLink(link *common.Link) (int, error) { j.lock.Lock() defer j.lock.Unlock() @@ -1030,7 +1159,16 @@ func (j *jsonConnector) findVotes(movie *common.Movie) []*common.Vote { func (j *jsonConnector) findUser(id int) *common.User { for _, u := range j.Users { if u.Id == id { - return u + return j.userFromJson(u) + } + } + return nil +} + +func (j *jsonConnector) findUserByName(name string) *common.User { + for _, u := range j.Users { + if strings.ToLower(u.Name) == strings.ToLower(name) { + return j.userFromJson(u) } } return nil @@ -1041,8 +1179,9 @@ func (j *jsonConnector) findUser(id int) *common.User { func (j *jsonConnector) UpdateUser(user *common.User) error { j.lock.Lock() defer j.lock.Unlock() - - j.Users[user.Id] = user + jUser := j.newJsonUser(user) + jUser.Id = user.Id + j.Users[user.Id] = jUser return j.save() } From 156e649f93c8a3c09febf3f1c638cd8834309410 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:22:22 +0100 Subject: [PATCH 17/67] Adapted deleteUser logic in admin page to new changes --- admin.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/admin.go b/admin.go index e486524..7e4783d 100644 --- a/admin.go +++ b/admin.go @@ -108,8 +108,10 @@ func (s *Server) adminDeleteUser(w http.ResponseWriter, r *http.Request, user *c s.l.Info("Deleting user %s", user) origName := user.Name user.Name = "[deleted]" - user.Password = "" - user.PassDate = time.Now() + for _, auth := range user.AuthMethods { + s.data.DeleteAuthMethod(auth.Id) + } + user.AuthMethods = []*common.AuthMethod{} user.Email = "" user.NotifyCycleEnd = false user.NotifyVoteSelection = false From 1adc1af7524a112cd6f0d65e05f9c1cdd5a6fa2d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:22:57 +0100 Subject: [PATCH 18/67] Adapted local password reset to new structure --- auth.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/auth.go b/auth.go index 3f4aac4..32b70f4 100644 --- a/auth.go +++ b/auth.go @@ -120,11 +120,19 @@ func (s *Server) handlerAuth(w http.ResponseWriter, r *http.Request) { return } - user.Password = s.hashPassword(pass1) - user.PassDate = time.Now() + var localAuth *common.AuthMethod + for _, auth := range user.AuthMethods { + if auth.Type == common.AUTH_LOCAL { + localAuth = auth + break + } + } + + localAuth.Password = s.hashPassword(pass1) + localAuth.PassDate = time.Now() - if err = s.data.UpdateUser(user); err != nil { - s.l.Error("Unable to save User with new password:", err) + if err = s.data.UpdateAuthMethod(localAuth); err != nil { + s.l.Error("Unable to save AuthMethod with new password:", err) s.doError(http.StatusInternalServerError, "Unable to update password", w, r) return } From f69d7d31fa761a2325d2f2a38914cb43fcff558a Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:23:37 +0100 Subject: [PATCH 19/67] Renamed constants --- common/AuthMethod.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/AuthMethod.go b/common/AuthMethod.go index fe58571..59624e3 100644 --- a/common/AuthMethod.go +++ b/common/AuthMethod.go @@ -5,10 +5,10 @@ import "time" type AuthType string const ( - DISCORD = "Discord" - TWITCH = "Twitch" - PATREON = "Patreon" - LOCAL = "Local" + AUTH_DISCORD = "Discord" + AUTH_TWITCH = "Twitch" + AUTH_PATREON = "Patreon" + AUTH_LOCAL = "Local" ) type AuthMethod struct { From 81a41bcc7636b4f68137569de4e082a7a5219f34 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:45:46 +0100 Subject: [PATCH 20/67] Reworked oauth user addition to use the AuthMethod struct --- oauth.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/oauth.go b/oauth.go index beb896a..5ff683c 100644 --- a/oauth.go +++ b/oauth.go @@ -4,7 +4,6 @@ import ( "encoding/json" "io/ioutil" "net/http" - "time" "github.com/zorchenhimer/MoviePolls/common" "golang.org/x/oauth2" @@ -114,18 +113,31 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } - // TODO check if the user already exists - // looping through all users and checking the auth data? - // how to determine if a request is for a given user? + auth := &common.AuthMethod{ + Type: common.AUTH_TWITCH, + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + id, err := s.data.AddAuthMethod(auth) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + auth.Id = id + + authSlice := []*common.AuthMethod{auth} - // Create a new User if no matching user is found newUser := &common.User{ Name: data["data"][0]["display_name"].(string), - Password: token.AccessToken, Email: data["data"][0]["email"].(string), NotifyCycleEnd: false, NotifyVoteSelection: false, - PassDate: time.Now(), + AuthMethods: authSlice, } s.l.Debug("adding user: %v", newUser) From d030676c45db80b51b07fd0ebe5e46ef51d2c325 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:46:27 +0100 Subject: [PATCH 21/67] Removed an old unused function --- server.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server.go b/server.go index bd8c7d7..d7073ac 100644 --- a/server.go +++ b/server.go @@ -274,12 +274,6 @@ func (s *Server) CheckAdminExists() (bool, error) { return false, nil } -func (s *Server) AddUser(user *common.User) error { - user.Password = s.hashPassword(user.Password) - _, err := s.data.AddUser(user) - return err -} - func (s *Server) handlerFavicon(w http.ResponseWriter, r *http.Request) { if common.FileExists("data/favicon.ico") { http.ServeFile(w, r, "data/favicon.ico") From 69ded62aed99340bd9482807e1a6919e14a51561 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:20:53 +0100 Subject: [PATCH 22/67] Adapted user.go to the new structure. --- user.go | 85 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/user.go b/user.go index 8e1c9c8..fca0fe6 100644 --- a/user.go +++ b/user.go @@ -108,43 +108,48 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { newPass1_raw := r.PostFormValue("PasswordNew1") newPass2_raw := r.PostFormValue("PasswordNew2") - if currentPass != user.Password { + localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) + if err != nil { data.ErrCurrentPass = true - data.PassError = append(data.PassError, "Invalid current password") - } - - if newPass1_raw == "" { - data.ErrNewPass = true - data.PassError = append(data.PassError, "New password cannot be blank") - } - - if newPass1_raw != newPass2_raw { - data.ErrNewPass = true - data.PassError = append(data.PassError, "Passwords do not match") - } + data.PassError = append(data.PassError, "No Password detected.") + } else { - if !(data.ErrCurrentPass || data.ErrNewPass || data.ErrEmail) { - // Change pass - data.SuccessMessage = "Password successfully changed" - user.Password = s.hashPassword(newPass1_raw) - user.PassDate = time.Now() + if currentPass != localAuth.Password { + data.ErrCurrentPass = true + data.PassError = append(data.PassError, "Invalid current password") + } - s.l.Info("new PassDate: %s", user.PassDate) + if newPass1_raw == "" { + data.ErrNewPass = true + data.PassError = append(data.PassError, "New password cannot be blank") + } - err = s.login(user, w, r) - if err != nil { - s.l.Error("Unable to login to session:", err) - s.doError(http.StatusInternalServerError, "Unable to update password", w, r) - return + if newPass1_raw != newPass2_raw { + data.ErrNewPass = true + data.PassError = append(data.PassError, "Passwords do not match") } + if !(data.ErrCurrentPass || data.ErrNewPass || data.ErrEmail) { + // Change pass + data.SuccessMessage = "Password successfully changed" + localAuth.Password = s.hashPassword(newPass1_raw) + localAuth.PassDate = time.Now() + + if err = s.data.UpdateAuthMethod(localAuth); err != nil { + s.l.Error("Unable to save User with new password:", err) + s.doError(http.StatusInternalServerError, "Unable to update password", w, r) + return + } + + s.l.Info("new PassDate: %s", localAuth.PassDate) + err = s.login(user, w, r) + if err != nil { + s.l.Error("Unable to login to session:", err) + s.doError(http.StatusInternalServerError, "Unable to update password", w, r) + return + } - if err = s.data.UpdateUser(user); err != nil { - s.l.Error("Unable to save User with new password:", err) - s.doError(http.StatusInternalServerError, "Unable to update password", w, r) - return } } - } else if formVal == "Notifications" { // Update notifications } @@ -201,7 +206,7 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { un := r.PostFormValue("Username") pw := r.PostFormValue("Password") - user, err = s.data.UserLogin(un, s.hashPassword(pw)) + user, err = s.data.UserLocalLogin(un, s.hashPassword(pw)) if err != nil { data.ErrorMessage = err.Error() } else { @@ -342,14 +347,30 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { data.ErrorMessage = append(data.ErrorMessage, "Email required for notifications") } + auth := &common.AuthMethod{ + Type: common.AUTH_LOCAL, + Password: s.hashPassword(pw1), + PassDate: time.Now(), + } + + id, err := s.data.AddAuthMethod(auth) + + if err != nil { + s.l.Error(err.Error()) + data.ErrorMessage = append(data.ErrorMessage, "Could not create new User, message the server admin") + } + + auth.Id = id + + authMethods := []*common.AuthMethod{auth} + if len(data.ErrorMessage) == 0 { newUser := &common.User{ Name: un, - Password: s.hashPassword(pw1), Email: email, + AuthMethods: authMethods, NotifyCycleEnd: data.ValNotifyEnd, NotifyVoteSelection: data.ValNotifySelected, - PassDate: time.Now(), } _, err = s.data.AddUser(newUser) From 571319218ba94ef53d44ce34b683ade62d25f3e6 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:21:22 +0100 Subject: [PATCH 23/67] Added a new utility function to quickly find the right auth struct --- common/user.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/user.go b/common/user.go index 5630b45..9fd4605 100644 --- a/common/user.go +++ b/common/user.go @@ -48,6 +48,15 @@ func (u User) IsMod() bool { return u.Privilege >= PRIV_ADMIN } +func (u User) GetAuthMethod(method AuthType) (*AuthMethod, error) { + for _, auth := range u.AuthMethods { + if auth.Type == method { + return auth, nil + } + } + return nil, fmt.Errorf("No AuthMethod with type %s found for user %s.", method, u.Name) +} + func (u User) String() string { return fmt.Sprintf( "User{Id:%d Name:%q Email:%q NotifyCycleEnd:%t NotifyVoteSelection:%t Privilege:%d}", From d430e395b5dcb94fbbf02c05d0220805424cc418 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:21:59 +0100 Subject: [PATCH 24/67] Adapted session.go to the new structure. --- session.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/session.go b/session.go index ea1dcbe..9b64b7b 100644 --- a/session.go +++ b/session.go @@ -24,7 +24,13 @@ func (s *Server) login(user *common.User, w http.ResponseWriter, r *http.Request return fmt.Errorf("Unable to get session from store: %v", err) } - gobbed, err := user.PassDate.GobEncode() + localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) + + if err != nil { + return err + } + + gobbed, err := localAuth.PassDate.GobEncode() if err != nil { return fmt.Errorf("Unable to gob PassDate") } @@ -76,7 +82,15 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. } passDate, _ := session.Values["PassDate"].(string) - gobbed, err := user.PassDate.GobEncode() + + localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) + + if err != nil { + s.l.Error(err.Error()) + return nil + } + + gobbed, err := localAuth.PassDate.GobEncode() if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != passDate { s.l.Info("User's PassDate did not match stored value") From f159e5796ff84cc818656d01e9a877b78601e2ae Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:22:24 +0100 Subject: [PATCH 25/67] I forgot to add the AuthMethods slice in the json - DUH --- data/json.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/data/json.go b/data/json.go index e0a37bf..c62e044 100644 --- a/data/json.go +++ b/data/json.go @@ -208,12 +208,13 @@ func newJsonConnector(filename string, l *common.Logger) (*jsonConnector, error) lock: &sync.RWMutex{}, Settings: map[string]configValue{}, - Cycles: map[int]jsonCycle{}, - Movies: map[int]jsonMovie{}, - Users: map[int]jsonUser{}, - Tags: map[int]*common.Tag{}, - Links: map[int]*common.Link{}, - l: l, + Cycles: map[int]jsonCycle{}, + Movies: map[int]jsonMovie{}, + Users: map[int]jsonUser{}, + Tags: map[int]*common.Tag{}, + Links: map[int]*common.Link{}, + AuthMethods: map[int]*common.AuthMethod{}, + l: l, } return j, j.save() From 49f6f6e8d404e78b47d1069bcbc11315c2fde45f Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:22:58 +0100 Subject: [PATCH 26/67] Added common/authmethod.go to the Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 95d8d38..a9f5391 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ SOURCES = \ admin.go \ api.go \ auth.go \ + common/authmethod.go \ common/cycle.go \ common/logger.go \ common/movie.go \ From 982b1f4bce2bb0523a8139c44eebb4157411d146 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Fri, 25 Dec 2020 15:45:25 +0100 Subject: [PATCH 27/67] Fixed a bug with user priviledges --- data/json.go | 1 + 1 file changed, 1 insertion(+) diff --git a/data/json.go b/data/json.go index c62e044..c32d890 100644 --- a/data/json.go +++ b/data/json.go @@ -600,6 +600,7 @@ func (j *jsonConnector) userFromJson(jUser jsonUser) *common.User { AuthMethods: authMethods, RateLimitOverride: jUser.RateLimitOverride, LastMovieAdd: *jUser.LastMovieAdd, + Privilege: common.PrivilegeLevel(jUser.Privilege), } return user From 3b0a0ebbda998ff0f8421bc8d401e3374b9a241e Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Fri, 25 Dec 2020 20:30:23 +0100 Subject: [PATCH 28/67] Fixed not saving when updating the AuthMethod --- data/json.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/json.go b/data/json.go index c32d890..2c7442f 100644 --- a/data/json.go +++ b/data/json.go @@ -930,8 +930,8 @@ func (j *jsonConnector) GetAuthMethod(id int) *common.AuthMethod { } func (j *jsonConnector) UpdateAuthMethod(authMethod *common.AuthMethod) error { - j.lock.RLock() - defer j.lock.RUnlock() + j.lock.Lock() + defer j.lock.Unlock() _, ok := j.AuthMethods[authMethod.Id] @@ -939,8 +939,10 @@ func (j *jsonConnector) UpdateAuthMethod(authMethod *common.AuthMethod) error { return fmt.Errorf("No AuthMethod with Id %d found.", authMethod.Id) } + j.l.Debug("Setting AuthMethod with ID %d to %v", authMethod.Id, authMethod) + j.AuthMethods[authMethod.Id] = authMethod - return nil + return j.save() } func (j *jsonConnector) DeleteTag(id int) { j.lock.Lock() From 4042c436f0ce3ef7d8d5282353c3743bff309315 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Fri, 25 Dec 2020 20:56:44 +0100 Subject: [PATCH 29/67] Fixed an unreleased db lock --- data/json.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/json.go b/data/json.go index 2c7442f..66e9a1f 100644 --- a/data/json.go +++ b/data/json.go @@ -956,6 +956,8 @@ func (j *jsonConnector) DeleteAuthMethod(id int) { defer j.lock.Unlock() delete(j.AuthMethods, id) + + j.save() } func (j *jsonConnector) nextTagId() int { From ff5d70e1666ccb9dacd30be684afbde836e6bf5b Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 19:39:11 +0100 Subject: [PATCH 30/67] Added a display on the account page to list active logins --- templates/account.html | 21 +++++++++++++++++++++ user.go | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/templates/account.html b/templates/account.html index 7edbada..b43cf73 100644 --- a/templates/account.html +++ b/templates/account.html @@ -43,6 +43,27 @@ */}} +
+
+
+ +
+
Your activated Login methods:
+
+ +
+
+ + +
+
+
+ +
Available votes: {{if .UnlimitedVotes}}∞{{else}}{{.AvailableVotes}}{{end}} (total: {{.TotalVotes}})
Your current votes
diff --git a/user.go b/user.go index fca0fe6..af2dc34 100644 --- a/user.go +++ b/user.go @@ -65,6 +65,8 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { data := struct { dataPageBase + User *common.User + TotalVotes int AvailableVotes int UnlimitedVotes bool @@ -84,6 +86,8 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { }{ dataPageBase: s.newPageBase("Account", w, r), + User: user, + TotalVotes: totalVotes, AvailableVotes: totalVotes - len(activeVotes), UnlimitedVotes: unlimited, From c54980d82be742a0770a93e7e7e60c21b4a1809d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 20:23:22 +0100 Subject: [PATCH 31/67] Added a new function to automatically add an AuthMethod to the user --- user.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/user.go b/user.go index af2dc34..61e0bd7 100644 --- a/user.go +++ b/user.go @@ -252,6 +252,31 @@ func (s *Server) handlerUserLogout(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } +func (s *Server) AddAuthMethodToUser(auth *common.AuthMethod, user *common.User) (*common.User, error) { + + if user.AuthMethods == nil { + user.AuthMethods = []*common.AuthMethod{} + } + + // Check if the user already has this authtype associated with him + if _, err := user.GetAuthMethod(auth.Type); err != nil { + + id, err := s.data.AddAuthMethod(auth) + + if err != nil { + return nil, fmt.Errorf("Could not create new AuthMethod %s for user %s", auth.Type, user.Name) + } + + auth.Id = id + + user.AuthMethods = append(user.AuthMethods, auth) + + return user, err + } else { + return nil, fmt.Errorf("AuthMethod %s is already associated with the user %s", auth.Type, user.Name) + } +} + func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { user := s.getSessionUser(w, r) if user != nil { @@ -357,27 +382,22 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { PassDate: time.Now(), } - id, err := s.data.AddAuthMethod(auth) - if err != nil { s.l.Error(err.Error()) data.ErrorMessage = append(data.ErrorMessage, "Could not create new User, message the server admin") } - auth.Id = id - - authMethods := []*common.AuthMethod{auth} - if len(data.ErrorMessage) == 0 { newUser := &common.User{ Name: un, Email: email, - AuthMethods: authMethods, NotifyCycleEnd: data.ValNotifyEnd, NotifyVoteSelection: data.ValNotifySelected, } - _, err = s.data.AddUser(newUser) + newUser, err = s.AddAuthMethodToUser(auth, newUser) + + newUser.Id, err = s.data.AddUser(newUser) if err != nil { data.ErrorMessage = append(data.ErrorMessage, err.Error()) } else { From 66a9f57d5db9797029923525b891ddb301ae9d6d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 21:53:45 +0100 Subject: [PATCH 32/67] Adapted session logic for AuthMethods --- auth.go | 2 +- oauth.go | 30 ++++++------ session.go | 132 +++++++++++++++++++++++++++++++++++++++++++++-------- user.go | 6 +-- 4 files changed, 133 insertions(+), 37 deletions(-) diff --git a/auth.go b/auth.go index 32b70f4..e805e6c 100644 --- a/auth.go +++ b/auth.go @@ -137,7 +137,7 @@ func (s *Server) handlerAuth(w http.ResponseWriter, r *http.Request) { return } - if err = s.login(user, w, r); err != nil { + if err = s.login(user, common.AUTH_LOCAL, w, r); err != nil { s.l.Error("Unable to login to session:", err) s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) return diff --git a/oauth.go b/oauth.go index 5ff683c..72a0576 100644 --- a/oauth.go +++ b/oauth.go @@ -120,7 +120,14 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque RefreshDate: token.Expiry, } - id, err := s.data.AddAuthMethod(auth) + newUser := &common.User{ + Name: data["data"][0]["display_name"].(string), + Email: data["data"][0]["email"].(string), + NotifyCycleEnd: false, + NotifyVoteSelection: false, + } + + newUser.Id, err = s.data.AddUser(newUser) if err != nil { s.l.Error(err.Error()) @@ -128,20 +135,15 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } - auth.Id = id + newUser, err = s.AddAuthMethodToUser(auth, newUser) - authSlice := []*common.AuthMethod{auth} - - newUser := &common.User{ - Name: data["data"][0]["display_name"].(string), - Email: data["data"][0]["email"].(string), - NotifyCycleEnd: false, - NotifyVoteSelection: false, - AuthMethods: authSlice, + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } - s.l.Debug("adding user: %v", newUser) - newUser.Id, err = s.data.AddUser(newUser) + err = s.data.UpdateUser(newUser) if err != nil { s.l.Error(err.Error()) @@ -149,8 +151,8 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } - s.l.Debug("logging in") - s.login(newUser, w, r) + s.l.Debug("logging in %v", newUser.Name) + s.login(newUser, common.AUTH_TWITCH, w, r) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return diff --git a/session.go b/session.go index 9b64b7b..13f873d 100644 --- a/session.go +++ b/session.go @@ -18,25 +18,52 @@ func (s *Server) logout(w http.ResponseWriter, r *http.Request) error { return delSession(session, w, r) } -func (s *Server) login(user *common.User, w http.ResponseWriter, r *http.Request) error { +func (s *Server) login(user *common.User, authType common.AuthType, w http.ResponseWriter, r *http.Request) error { session, err := s.cookies.Get(r, SessionName) if err != nil { return fmt.Errorf("Unable to get session from store: %v", err) } - localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) - + auth, err := user.GetAuthMethod(authType) if err != nil { return err } - gobbed, err := localAuth.PassDate.GobEncode() - if err != nil { - return fmt.Errorf("Unable to gob PassDate") - } - session.Values["UserId"] = user.Id - session.Values["PassDate"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + + switch authType { + case common.AUTH_LOCAL: + + gobbed, err := auth.PassDate.GobEncode() + if err != nil { + return fmt.Errorf("Unable to gob PassDate") + } + + session.Values["PassDate"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + case common.AUTH_TWITCH: + gobbed, err := auth.RefreshDate.GobEncode() + if err != nil { + return fmt.Errorf("Unable to gob RefreshDate") + } + + session.Values["RefreshDate_Twitch"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + case common.AUTH_PATREON: + gobbed, err := auth.RefreshDate.GobEncode() + if err != nil { + return fmt.Errorf("Unable to gob RefreshDate") + } + + session.Values["RefreshDate_Patreon"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + case common.AUTH_DISCORD: + gobbed, err := auth.RefreshDate.GobEncode() + if err != nil { + return fmt.Errorf("Unable to gob RefreshDate") + } + + session.Values["RefreshDate_Discord"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + default: + return fmt.Errorf("Login without a valid auth method") + } return session.Save(r, w) } @@ -44,6 +71,9 @@ func (s *Server) login(user *common.User, w http.ResponseWriter, r *http.Request func delSession(session *sessions.Session, w http.ResponseWriter, r *http.Request) error { delete(session.Values, "UserId") delete(session.Values, "PassDate") + delete(session.Values, "RefreshDate_Discord") + delete(session.Values, "RefreshDate_Twitch") + delete(session.Values, "RefreshDate_Patreon") return session.Save(r, w) } @@ -81,23 +111,87 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. return nil } + // I am sorry - CptPie passDate, _ := session.Values["PassDate"].(string) + refreshTwitch, _ := session.Values["RefreshDate_Twitch"].(string) + refreshDiscord, _ := session.Values["RefreshDate_Discord"].(string) + refreshPatreon, _ := session.Values["RefreshDate_Patreon"].(string) - localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) + if passDate != "" { + localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) - if err != nil { - s.l.Error(err.Error()) - return nil - } + if err != nil { + s.l.Error(err.Error()) + return nil + } - gobbed, err := localAuth.PassDate.GobEncode() + gobbed, err := localAuth.PassDate.GobEncode() + + if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != passDate { + s.l.Info("User's PassDate did not match stored value") + err = delSession(session, w, r) + if err != nil { + s.l.Error("Unable to delete cookie: %v", err) + } + return nil + } + } else if refreshTwitch != "" { + twitchAuth, err := user.GetAuthMethod(common.AUTH_TWITCH) - if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != passDate { - s.l.Info("User's PassDate did not match stored value") - err = delSession(session, w, r) if err != nil { - s.l.Error("Unable to delete cookie: %v", err) + s.l.Error(err.Error()) + return nil + } + + gobbed, err := twitchAuth.RefreshDate.GobEncode() + + if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshTwitch { + s.l.Info("User's RefreshDate did not match stored value") + err = delSession(session, w, r) + if err != nil { + s.l.Error("Unable to delete cookie: %v", err) + } + return nil + } + } else if refreshDiscord != "" { + discordAuth, err := user.GetAuthMethod(common.AUTH_DISCORD) + + if err != nil { + s.l.Error(err.Error()) + return nil + } + + gobbed, err := discordAuth.RefreshDate.GobEncode() + + if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshDiscord { + s.l.Info("User's RefreshDate did not match stored value") + err = delSession(session, w, r) + if err != nil { + s.l.Error("Unable to delete cookie: %v", err) + } + return nil + } + } else if refreshPatreon != "" { + patreonAuth, err := user.GetAuthMethod(common.AUTH_PATREON) + + if err != nil { + s.l.Error(err.Error()) + return nil + } + + gobbed, err := patreonAuth.RefreshDate.GobEncode() + + if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshPatreon { + s.l.Info("User's RefreshDate did not match stored value") + err = delSession(session, w, r) + if err != nil { + s.l.Error("Unable to delete cookie: %v", err) + } + return nil } + } else { + //WTF MAN + s.l.Error("No valid login method detected") return nil } diff --git a/user.go b/user.go index 61e0bd7..8fb311f 100644 --- a/user.go +++ b/user.go @@ -145,7 +145,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { } s.l.Info("new PassDate: %s", localAuth.PassDate) - err = s.login(user, w, r) + err = s.login(user, common.AUTH_LOCAL, w, r) if err != nil { s.l.Error("Unable to login to session:", err) s.doError(http.StatusInternalServerError, "Unable to update password", w, r) @@ -222,7 +222,7 @@ func (s *Server) handlerUserLogin(w http.ResponseWriter, r *http.Request) { } if user != nil { - err = s.login(user, w, r) + err = s.login(user, common.AUTH_LOCAL, w, r) if err != nil { s.l.Error("Unable to login: %v", err) s.doError(http.StatusInternalServerError, "Unable to login", w, r) @@ -401,7 +401,7 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { if err != nil { data.ErrorMessage = append(data.ErrorMessage, err.Error()) } else { - err = s.login(newUser, w, r) + err = s.login(newUser, common.AUTH_LOCAL, w, r) if err != nil { s.l.Error("Unable to login to session: %v", err) s.doError(http.StatusInternalServerError, "Login error", w, r) From 11e2baba00de490a3d50c7ff5e4679bf2adfc7b8 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 21:54:35 +0100 Subject: [PATCH 33/67] Added ExtId field to AuthMethod struct --- common/AuthMethod.go | 1 + oauth.go | 1 + 2 files changed, 2 insertions(+) diff --git a/common/AuthMethod.go b/common/AuthMethod.go index 59624e3..3b215e1 100644 --- a/common/AuthMethod.go +++ b/common/AuthMethod.go @@ -13,6 +13,7 @@ const ( type AuthMethod struct { Id int + ExtId string Type AuthType Password string PassDate time.Time diff --git a/oauth.go b/oauth.go index 72a0576..8fd8674 100644 --- a/oauth.go +++ b/oauth.go @@ -115,6 +115,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque auth := &common.AuthMethod{ Type: common.AUTH_TWITCH, + ExtId: data["data"][0]["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, RefreshDate: token.Expiry, From 2d1ea6c1fb8e3397ef628cae29f6d9a047e06059 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 22:47:20 +0100 Subject: [PATCH 34/67] Added CheckOauthUsage function, implemented twitch login --- data/connector.go | 8 +++++--- data/json.go | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/data/connector.go b/data/connector.go index 0a6f777..d0c3c0c 100644 --- a/data/connector.go +++ b/data/connector.go @@ -105,9 +105,11 @@ type DataConnector interface { // ################ UserLocalLogin(name, hashedPw string) (*common.User, error) - UserDiscordLogin(name, token string) (*common.User, error) - UserTwitchLogin(name, token string) (*common.User, error) - UserPatreonLogin(name, token string) (*common.User, error) + UserDiscordLogin(extid string) (*common.User, error) + UserTwitchLogin(extid string) (*common.User, error) + UserPatreonLogin(extid string) (*common.User, error) + + CheckOauthUsage(id string, authtype common.AuthType) bool SearchMovieTitles(query string) ([]*common.Movie, error) diff --git a/data/json.go b/data/json.go index 66e9a1f..d4466d5 100644 --- a/data/json.go +++ b/data/json.go @@ -651,15 +651,35 @@ func (j *jsonConnector) UserLocalLogin(name, hashedPw string) (*common.User, err return nil, fmt.Errorf("Invalid login credentials") } -func (j *jsonConnector) UserDiscordLogin(name, hashedPw string) (*common.User, error) { +func (j *jsonConnector) UserDiscordLogin(extid string) (*common.User, error) { return nil, fmt.Errorf("Not implemented") } -func (j *jsonConnector) UserTwitchLogin(name, hashedPw string) (*common.User, error) { - return nil, fmt.Errorf("Not implemented") +func (j *jsonConnector) UserTwitchLogin(extid string) (*common.User, error) { + j.lock.Lock() + defer j.lock.Unlock() + + //TODO refreshing + + var id int + for _, auth := range j.AuthMethods { + if auth.ExtId == extid { + id = auth.Id + break + } + } + + for _, user := range j.Users { + for _, auth := range user.AuthMethods { + if auth == id { + return j.findUser(user.Id), nil + } + } + } + return nil, fmt.Errorf("No user found with corresponding extid") } -func (j *jsonConnector) UserPatreonLogin(name, hashedPw string) (*common.User, error) { +func (j *jsonConnector) UserPatreonLogin(extid string) (*common.User, error) { return nil, fmt.Errorf("Not implemented") } @@ -1490,3 +1510,17 @@ func (j *jsonConnector) Test_GetUserVotes(userId int) ([]*common.Vote, error) { } return votes, nil } + +func (j *jsonConnector) CheckOauthUsage(id string, authType common.AuthType) bool { + j.lock.Lock() + defer j.lock.Unlock() + + for _, auth := range j.AuthMethods { + if auth.Type == authType { + if auth.ExtId == id { + return true + } + } + } + return false +} From 24c991c94521c91dbd2c91dea4fa6da4268a282b Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 30 Dec 2020 22:48:26 +0100 Subject: [PATCH 35/67] Seperated oauth signup and login to seperate pages+handlers --- oauth.go | 106 +++++++++++++++++++++++++------------- server.go | 3 +- templates/newaccount.html | 14 +++++ user.go | 31 +++++++++++ 4 files changed, 118 insertions(+), 36 deletions(-) diff --git a/oauth.go b/oauth.go index 8fd8674..e83c001 100644 --- a/oauth.go +++ b/oauth.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "net/http" + "strings" "github.com/zorchenhimer/MoviePolls/common" "golang.org/x/oauth2" @@ -47,16 +48,32 @@ func (s *Server) initOauth() error { return nil } -func (s *Server) handlerTwitchOAuth(w http.ResponseWriter, r *http.Request) { +func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) // Generate a new state string for each login attempt and store it in the state list - oauthStateString := getCryptRandKey(32) + oauthStateString := "login_" + getCryptRandKey(32) openStates = append(openStates, oauthStateString) // Handle the Oauth redirect url := twitchOAuthConfig.AuthCodeURL(oauthStateString) http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("twitch login") +} + +func (s *Server) handlerTwitchOAuthSignup(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("twitch signup") } func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Request) { @@ -113,48 +130,67 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } - auth := &common.AuthMethod{ - Type: common.AUTH_TWITCH, - ExtId: data["data"][0]["id"].(string), - AuthToken: token.AccessToken, - RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, - } + if strings.HasPrefix(state, "signup_") { - newUser := &common.User{ - Name: data["data"][0]["display_name"].(string), - Email: data["data"][0]["email"].(string), - NotifyCycleEnd: false, - NotifyVoteSelection: false, - } + s.l.Debug("signup prefix") - newUser.Id, err = s.data.AddUser(newUser) + auth := &common.AuthMethod{ + Type: common.AUTH_TWITCH, + ExtId: data["data"][0]["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } - if err != nil { - s.l.Error(err.Error()) - http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) - return - } + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { - newUser, err = s.AddAuthMethodToUser(auth, newUser) + newUser := &common.User{ + Name: data["data"][0]["display_name"].(string), + Email: data["data"][0]["email"].(string), + NotifyCycleEnd: false, + NotifyVoteSelection: false, + } - if err != nil { - s.l.Error(err.Error()) - http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) - return - } + newUser.Id, err = s.data.AddUser(newUser) - err = s.data.UpdateUser(newUser) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } - if err != nil { - s.l.Error(err.Error()) - http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) - return - } + newUser, err = s.AddAuthMethodToUser(auth, newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(newUser) - s.l.Debug("logging in %v", newUser.Name) - s.login(newUser, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + s.l.Debug("logging in %v", newUser.Name) + s.login(newUser, common.AUTH_TWITCH, w, r) + } else { + s.l.Debug("AuthMethod already used") + } + } else if strings.HasPrefix(state, "login_") { + s.l.Debug("login prefix") + user, err := s.data.UserTwitchLogin(data["data"][0]["id"].(string)) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + s.l.Debug("logging in %v", user.Name) + s.login(user, common.AUTH_TWITCH, w, r) + } http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } diff --git a/server.go b/server.go index d7073ac..90d7289 100644 --- a/server.go +++ b/server.go @@ -206,11 +206,12 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user", server.handlerUser) mux.HandleFunc("/user/login", server.handlerUserLogin) - mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuth) + mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuthLogin) mux.HandleFunc("/user/login/twitch/callback", server.handlerTwitchOAuthCallback) mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) + mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) mux.HandleFunc("/vote/", server.handlerVote) mux.HandleFunc("/", server.handlerRoot) diff --git a/templates/newaccount.html b/templates/newaccount.html index cd76350..65d10ef 100644 --- a/templates/newaccount.html +++ b/templates/newaccount.html @@ -22,6 +22,20 @@
+ {{if .OAuth}} +
+ {{if .TwitchOAuth}} + Login with Twitch + {{end}} + {{if .DiscordOAuth}} + Login with Discord + {{end}} + {{if .PatreonOAuth}} + Login with Patreon + {{end}} +
+ {{end}} + {{/* Disable this stuff for now
diff --git a/user.go b/user.go index 8fb311f..4ed034a 100644 --- a/user.go +++ b/user.go @@ -292,6 +292,11 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { ErrPass bool ErrEmail bool + OAuth bool + TwitchOAuth bool + DiscordOAuth bool + PatreonOAuth bool + ValName string ValEmail string ValNotifyEnd bool @@ -302,6 +307,32 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { doRedirect := false + twitchAuth, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) + return + } + data.TwitchOAuth = twitchAuth + + discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) + return + } + data.DiscordOAuth = discordAuth + + patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) + return + } + data.PatreonOAuth = patreonAuth + + data.OAuth = twitchAuth || discordAuth || patreonAuth + if r.Method == "POST" { err := r.ParseForm() if err != nil { From 210b959d3fb27164a2c82f0c161a373bc442ad49 Mon Sep 17 00:00:00 2001 From: CptPie <3438606+CptPie@users.noreply.github.com> Date: Sun, 3 Jan 2021 04:13:18 +0100 Subject: [PATCH 36/67] Renamed AuthMethod file --- common/{AuthMethod.go => authmethod.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename common/{AuthMethod.go => authmethod.go} (100%) diff --git a/common/AuthMethod.go b/common/authmethod.go similarity index 100% rename from common/AuthMethod.go rename to common/authmethod.go From 240d73dc5d409ace1c6102192d5bf747bbb11830 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sun, 3 Jan 2021 07:47:00 +0100 Subject: [PATCH 37/67] Added the ability to link an existing account with Oauth Also prepared everything to delete linked oauths --- oauth.go | 51 ++++++++++++++++++++++++++++ server.go | 1 + templates/account.html | 75 +++++++++++++++++++++++++++++++++++++++--- user.go | 31 +++++++++++++++++ 4 files changed, 153 insertions(+), 5 deletions(-) diff --git a/oauth.go b/oauth.go index e83c001..ed52729 100644 --- a/oauth.go +++ b/oauth.go @@ -76,6 +76,20 @@ func (s *Server) handlerTwitchOAuthSignup(w http.ResponseWriter, r *http.Request s.l.Debug("twitch signup") } +func (s *Server) handlerTwitchOAuthAdd(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("twitch add") +} + func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") @@ -190,6 +204,43 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque } s.l.Debug("logging in %v", user.Name) s.login(user, common.AUTH_TWITCH, w, r) + } else if strings.HasPrefix(state, "add_") { + s.l.Debug("add prefix") + + user := s.getSessionUser(w, r) + + auth := &common.AuthMethod{ + Type: common.AUTH_TWITCH, + ExtId: data["data"][0]["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + _, err = s.AddAuthMethodToUser(auth, user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("User %s already has linked auth %s", user.Name, auth.Type) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return diff --git a/server.go b/server.go index 90d7289..b1bb60c 100644 --- a/server.go +++ b/server.go @@ -212,6 +212,7 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) + mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) mux.HandleFunc("/vote/", server.handlerVote) mux.HandleFunc("/", server.handlerRoot) diff --git a/templates/account.html b/templates/account.html index b43cf73..0b57b39 100644 --- a/templates/account.html +++ b/templates/account.html @@ -51,13 +51,78 @@
Your activated Login methods:
+ {{if .User}} + {{range .User.AuthMethods}}
  • {{.Type}}
  • {{end}} + {{else}}
  • No Auth Methods :c
  • {{end}} +
    - + {{if .OAuth}} +
    + + {{if .TwitchOAuth}} +
    + {{$twitch := ""}} + {{if .User}} + + {{range .User.AuthMethods}} + {{if eq .Type "Twitch" }} + {{$twitch = .Type}} + {{ end }} + {{ end }} + + {{ if ne $twitch "" }} + Unlink Account with Twitch + {{ else }} + Link Account with Twitch + {{ end }} + {{ end }} +
    + {{ end }} + + + {{if .DiscordOAuth}} +
    + {{$discord := ""}} + {{if .User}} + + {{range .User.AuthMethods}} + {{if eq .Type "Discord" }} + {{$discord = .Type}} + {{ end }} + {{ end }} + + {{ if ne $discord "" }} + Unlink Account with Discord + {{ else }} + Link Account with Discord + {{ end }} + {{ end }} +
    + {{end}} + + + {{if .PatreonOAuth}} +
    + {{$patreon := ""}} + {{if .User}} + + {{range .User.AuthMethods}} + {{if eq .Type "Patreon" }} + {{$patreon = .Type}} + {{ end }} + {{ end }} + + {{ if ne $patreon "" }} + Unlink Account with Patreon + {{ else }} + Link Account with Patreon + {{ end }} + {{ end }} +
    + {{end}} +
    + {{end}}

    diff --git a/user.go b/user.go index 4ed034a..ccc86fd 100644 --- a/user.go +++ b/user.go @@ -71,6 +71,11 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { AvailableVotes int UnlimitedVotes bool + OAuth bool + TwitchOAuth bool + DiscordOAuth bool + PatreonOAuth bool + ActiveVotes []*common.Movie WatchedVotes []*common.Movie AddedMovies []*common.Movie @@ -97,6 +102,32 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { AddedMovies: addedMovies, } + twitchAuth, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) + return + } + data.TwitchOAuth = twitchAuth + + discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) + return + } + data.DiscordOAuth = discordAuth + + patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) + return + } + data.PatreonOAuth = patreonAuth + + data.OAuth = twitchAuth || discordAuth || patreonAuth + if r.Method == "POST" { err := r.ParseForm() if err != nil { From 983a5834790d23e11d9a5618e62b177f0e8a62d8 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sun, 3 Jan 2021 08:56:08 +0100 Subject: [PATCH 38/67] Added ability to remove linked Oauth method Also fixed some redirects --- oauth.go | 74 ++++++++++++++++++++++++++++++++++++++++++++----------- server.go | 1 + user.go | 26 +++++++++++++++++++ 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/oauth.go b/oauth.go index ed52729..b51b811 100644 --- a/oauth.go +++ b/oauth.go @@ -90,6 +90,44 @@ func (s *Server) handlerTwitchOAuthAdd(w http.ResponseWriter, r *http.Request) { s.l.Debug("twitch add") } +func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request) { + s.l.Debug("twitch remove") + + user := s.getSessionUser(w, r) + + auth, err := user.GetAuthMethod(common.AUTH_TWITCH) + + if err != nil { + s.l.Info("User %s does not have Twitch Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Twitch Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + user, err = s.RemoveAuthMethodFromUser(auth, user) + + if err != nil { + s.l.Info("Could not remove Twitch Oauth from user. %s", err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + if err != nil { + s.l.Info("Could not update user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return +} + func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") @@ -191,8 +229,10 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque s.l.Debug("logging in %v", newUser.Name) s.login(newUser, common.AUTH_TWITCH, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else { s.l.Debug("AuthMethod already used") + http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) } } else if strings.HasPrefix(state, "login_") { s.l.Debug("login prefix") @@ -204,6 +244,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque } s.l.Debug("logging in %v", user.Name) s.login(user, common.AUTH_TWITCH, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else if strings.HasPrefix(state, "add_") { s.l.Debug("add prefix") @@ -218,31 +259,36 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { - _, err = s.AddAuthMethodToUser(auth, user) - + _, err = user.GetAuthMethod(auth.Type) if err != nil { - s.l.Error(err.Error()) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } - - err = s.data.UpdateUser(user) - - if err != nil { - s.l.Error(err.Error()) + _, err = s.AddAuthMethodToUser(auth, user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("User %s already has linked auth %s", user.Name, auth.Type) + s.l.Error("The provided Oauth login is already used") http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return - } - http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } diff --git a/server.go b/server.go index b1bb60c..a26241d 100644 --- a/server.go +++ b/server.go @@ -213,6 +213,7 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/new", server.handlerUserNew) mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) + mux.HandleFunc("/user/remove/twitch", server.handlerTwitchOAuthRemove) mux.HandleFunc("/vote/", server.handlerVote) mux.HandleFunc("/", server.handlerRoot) diff --git a/user.go b/user.go index ccc86fd..6ccc922 100644 --- a/user.go +++ b/user.go @@ -308,6 +308,32 @@ func (s *Server) AddAuthMethodToUser(auth *common.AuthMethod, user *common.User) } } +func (s *Server) RemoveAuthMethodFromUser(auth *common.AuthMethod, user *common.User) (*common.User, error) { + + if user.AuthMethods == nil { + user.AuthMethods = []*common.AuthMethod{} + } + + // Check if the user already has this authtype associated with him + _, err := user.GetAuthMethod(auth.Type) + if err != nil { + return nil, fmt.Errorf("AuthMethod %s is not associated with the user %s", auth.Type, user.Name) + } + s.data.DeleteAuthMethod(auth.Id) + + // thanks golang for not having a delete method for slices ... + oldauths := user.AuthMethods + newAuths := []*common.AuthMethod{} + for _, a := range oldauths { + if a != auth { + newAuths = append(newAuths, a) + } + } + + user.AuthMethods = newAuths + + return user, err +} func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { user := s.getSessionUser(w, r) if user != nil { From 17029021ea0cee8e14617863a430e436ef8c4f43 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sun, 3 Jan 2021 09:11:14 +0100 Subject: [PATCH 39/67] Improved variable naming and extracted ... logic from the template into the calling function --- templates/account.html | 67 ++++++++++++------------------------------ user.go | 30 ++++++++++++++----- 2 files changed, 40 insertions(+), 57 deletions(-) diff --git a/templates/account.html b/templates/account.html index 0b57b39..21649bc 100644 --- a/templates/account.html +++ b/templates/account.html @@ -57,67 +57,37 @@ - {{if .OAuth}} + {{if .OAuthEnabled}}
    - {{if .TwitchOAuth}} + {{if .TwitchOAuthEnabled}}
    - {{$twitch := ""}} - {{if .User}} - - {{range .User.AuthMethods}} - {{if eq .Type "Twitch" }} - {{$twitch = .Type}} - {{ end }} - {{ end }} - - {{ if ne $twitch "" }} - Unlink Account with Twitch - {{ else }} - Link Account with Twitch - {{ end }} + {{ if .HasTwitch}} + Unlink Account with Twitch + {{ else }} + Link Account with Twitch {{ end }}
    {{ end }} - {{if .DiscordOAuth}} + {{if .DiscordOAuthEnabled}}
    - {{$discord := ""}} - {{if .User}} - - {{range .User.AuthMethods}} - {{if eq .Type "Discord" }} - {{$discord = .Type}} - {{ end }} - {{ end }} - - {{ if ne $discord "" }} - Unlink Account with Discord - {{ else }} - Link Account with Discord - {{ end }} + {{ if .HasDiscord }} + Unlink Account with Discord + {{ else }} + Link Account with Discord {{ end }}
    {{end}} - {{if .PatreonOAuth}} + {{if .PatreonOAuthEnabled}}
    - {{$patreon := ""}} - {{if .User}} - - {{range .User.AuthMethods}} - {{if eq .Type "Patreon" }} - {{$patreon = .Type}} - {{ end }} - {{ end }} - - {{ if ne $patreon "" }} - Unlink Account with Patreon - {{ else }} - Link Account with Patreon - {{ end }} + {{ if .HasPatreon}} + Unlink Account with Patreon + {{ else }} + Link Account with Patreon {{ end }}
    {{end}} @@ -127,9 +97,8 @@


    - - -
    + +
    Available votes: {{if .UnlimitedVotes}}∞{{else}}{{.AvailableVotes}}{{end}} (total: {{.TotalVotes}})
    Your current votes
    diff --git a/user.go b/user.go index 6ccc922..733e7b2 100644 --- a/user.go +++ b/user.go @@ -71,10 +71,15 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { AvailableVotes int UnlimitedVotes bool - OAuth bool - TwitchOAuth bool - DiscordOAuth bool - PatreonOAuth bool + OAuthEnabled bool + TwitchOAuthEnabled bool + DiscordOAuthEnabled bool + PatreonOAuthEnabled bool + + HasLocal bool + HasTwitch bool + HasDiscord bool + HasPatreon bool ActiveVotes []*common.Movie WatchedVotes []*common.Movie @@ -108,7 +113,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) return } - data.TwitchOAuth = twitchAuth + data.TwitchOAuthEnabled = twitchAuth discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) if err != nil { @@ -116,7 +121,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) return } - data.DiscordOAuth = discordAuth + data.DiscordOAuthEnabled = discordAuth patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) if err != nil { @@ -124,9 +129,18 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) return } - data.PatreonOAuth = patreonAuth + data.PatreonOAuthEnabled = patreonAuth - data.OAuth = twitchAuth || discordAuth || patreonAuth + data.OAuthEnabled = twitchAuth || discordAuth || patreonAuth + + _, err = user.GetAuthMethod(common.AUTH_LOCAL) + data.HasLocal = err == nil + _, err = user.GetAuthMethod(common.AUTH_TWITCH) + data.HasTwitch = err == nil + _, err = user.GetAuthMethod(common.AUTH_DISCORD) + data.HasDiscord = err == nil + _, err = user.GetAuthMethod(common.AUTH_PATREON) + data.HasPatreon = err == nil if r.Method == "POST" { err := r.ParseForm() From 25f97e7315cf66a7081b8fc83233d1980579a99d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 4 Jan 2021 21:07:26 +0100 Subject: [PATCH 40/67] Added the ability to add/remove localauth --- oauth.go | 66 ++++++++++++++++++++++++++++++++++++++++++ server.go | 1 + templates/account.html | 15 +++++++++- user.go | 54 ++++++++++++++++++++++++++++++++-- 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/oauth.go b/oauth.go index b51b811..4aad6e9 100644 --- a/oauth.go +++ b/oauth.go @@ -48,6 +48,72 @@ func (s *Server) initOauth() error { return nil } +func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) { + s.l.Debug("local remove") + + user := s.getSessionUser(w, r) + + auth, err := user.GetAuthMethod(common.AUTH_LOCAL) + + if err != nil { + s.l.Info("User %s does not have a password associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has the local Authmethod associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + user, err = s.RemoveAuthMethodFromUser(auth, user) + + if err != nil { + s.l.Info("Could not remove password from user. %s", err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + if err != nil { + s.l.Info("Could not update user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.logout(w, r) + if err != nil { + s.l.Info("Could not logout user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { + err = s.login(user, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { + err = s.login(user, common.AUTH_DISCORD, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { + err = s.login(user, common.AUTH_PATREON, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return +} func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) diff --git a/server.go b/server.go index a26241d..2c9fa42 100644 --- a/server.go +++ b/server.go @@ -214,6 +214,7 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) mux.HandleFunc("/user/remove/twitch", server.handlerTwitchOAuthRemove) + mux.HandleFunc("/user/remove/local", server.handlerLocalAuthRemove) mux.HandleFunc("/vote/", server.handlerVote) mux.HandleFunc("/", server.handlerRoot) diff --git a/templates/account.html b/templates/account.html index 21649bc..8a126f6 100644 --- a/templates/account.html +++ b/templates/account.html @@ -3,9 +3,9 @@ {{define "body"}}
    + {{ if .HasLocal }}
    -
    Change password
    {{if .PassError}}
      {{range .PassError}}
    • {{.}}
    • {{end}}
    {{end}}
    @@ -17,6 +17,19 @@
    + Remove Password Login + {{ else }} +
    + +
    Set password for local Login
    + {{if .PassError}}
      {{range .PassError}}
    • {{.}}
    • {{end}}
    {{end}} +
    +
    +
    +
    +
    +
    + {{ end }}
    {{/* diff --git a/user.go b/user.go index 733e7b2..5829dae 100644 --- a/user.go +++ b/user.go @@ -196,14 +196,64 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { s.doError(http.StatusInternalServerError, "Unable to update password", w, r) return } - } } } else if formVal == "Notifications" { // Update notifications + } else if formVal == "SetPassword" { + pass1_raw := r.PostFormValue("Password1") + pass2_raw := r.PostFormValue("Password2") + + _, err := user.GetAuthMethod(common.AUTH_LOCAL) + if err == nil { + data.ErrCurrentPass = true + data.PassError = append(data.PassError, "Existing password detected. (how did you end up here anyways?)") + } else { + localAuth := &common.AuthMethod{ + Type: common.AUTH_LOCAL, + } + + if pass1_raw == "" { + data.ErrNewPass = true + data.PassError = append(data.PassError, "New password cannot be blank") + } + + if pass1_raw != pass2_raw { + data.ErrNewPass = true + data.PassError = append(data.PassError, "Passwords do not match") + } + if !(data.ErrCurrentPass || data.ErrNewPass || data.ErrEmail) { + // Change pass + data.SuccessMessage = "Password successfully changed" + localAuth.Password = s.hashPassword(pass1_raw) + localAuth.PassDate = time.Now() + s.l.Info("new PassDate: %s", localAuth.PassDate) + + user, err = s.AddAuthMethodToUser(localAuth, user) + + if err != nil { + s.l.Error("Unable to add AuthMethod %s to user %s", localAuth.Type, user.Name) + s.doError(http.StatusInternalServerError, "Unable to link password to user", w, r) + } + + s.data.UpdateUser(user) + + if err != nil { + s.l.Error("Unable to update user %s", user.Name) + s.doError(http.StatusInternalServerError, "Unable to update user", w, r) + } + + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Error("Unable to login to session:", err) + s.doError(http.StatusInternalServerError, "Unable to update password", w, r) + } + + http.Redirect(w, r, "/user", http.StatusFound) + } + } } } - if err := s.executeTemplate(w, "account", data); err != nil { s.l.Error("Error rendering template: %v", err) } From da866cbf7c6b6d26252c793058dd8dee7ee0d48a Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 4 Jan 2021 21:25:00 +0100 Subject: [PATCH 41/67] Fixed user purge to delete auth methods --- data/json.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/json.go b/data/json.go index d4466d5..4d80146 100644 --- a/data/json.go +++ b/data/json.go @@ -1412,6 +1412,11 @@ func (j *jsonConnector) PurgeUser(userId int) error { count++ } } + + for _, auth := range j.findUser(userId).AuthMethods { + delete(j.AuthMethods, auth.Id) + } + j.Votes = newVotes j.l.Info("Purged %d votes", count) From c078a4f14a9c67b0276291e7e78982454201a6c2 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:59:19 +0100 Subject: [PATCH 42/67] Implemented Discord Oauth --- data/json.go | 22 +++- oauth.go | 335 ++++++++++++++++++++++++++++++++++++++++++++++++++- server.go | 6 + 3 files changed, 360 insertions(+), 3 deletions(-) diff --git a/data/json.go b/data/json.go index 4d80146..213672e 100644 --- a/data/json.go +++ b/data/json.go @@ -652,7 +652,27 @@ func (j *jsonConnector) UserLocalLogin(name, hashedPw string) (*common.User, err } func (j *jsonConnector) UserDiscordLogin(extid string) (*common.User, error) { - return nil, fmt.Errorf("Not implemented") + j.lock.Lock() + defer j.lock.Unlock() + + //TODO refreshing + + var id int + for _, auth := range j.AuthMethods { + if auth.ExtId == extid { + id = auth.Id + break + } + } + + for _, user := range j.Users { + for _, auth := range user.AuthMethods { + if auth == id { + return j.findUser(user.Id), nil + } + } + } + return nil, fmt.Errorf("No user found with corresponding extid") } func (j *jsonConnector) UserTwitchLogin(extid string) (*common.User, error) { diff --git a/oauth.go b/oauth.go index 4aad6e9..b261bc8 100644 --- a/oauth.go +++ b/oauth.go @@ -19,6 +19,12 @@ var patreonOAuthConfig = &oauth2.Config{} // var oauthStateString string var openStates = []string{} +// Welp we need to do the endpoints ourself i guess ... +var discordEndpoint = oauth2.Endpoint{ + AuthURL: "https://discord.com/api/oauth2/authorize", + TokenURL: "https://discord.com/api/oauth2/token", +} + func (s *Server) initOauth() error { twitchOauthEnabled, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) if err != nil { @@ -40,7 +46,32 @@ func (s *Server) initOauth() error { ClientID: twitchClientID, ClientSecret: twitchClientSecret, Scopes: []string{"user:read:email"}, - Endpoint: twitch.Endpoint, + Endpoint: twitch.Endpoint, //this endpoint is predefined in the oauth2 package + } + } + + discordOAuthEnabled, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) + if err != nil { + return err + } + + if discordOAuthEnabled { + discordClientID, err := s.data.GetCfgString(ConfigDiscordOauthClientID, DefaultDiscordOauthClientID) + if err != nil { + return err + } + + discordClientSecret, err := s.data.GetCfgString(ConfigDiscordOauthClientSecret, DefaultDiscordOauthClientSecret) + if err != nil { + return err + } + + discordOAuthConfig = &oauth2.Config{ + RedirectURL: "http://localhost:8090/user/login/discord/callback", + ClientID: discordClientID, + ClientSecret: discordClientSecret, + Scopes: []string{"email", "identify"}, + Endpoint: discordEndpoint, } } // TODO cry in a corner and figure out how to do this stuff for discord and patreon @@ -114,6 +145,7 @@ func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } + func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -190,6 +222,35 @@ func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request return } + err = s.logout(w, r) + if err != nil { + s.l.Info("Could not logout user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { + err = s.login(user, common.AUTH_DISCORD, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { + err = s.login(user, common.AUTH_PATREON, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -358,8 +419,278 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } -func (s *Server) handlerDiscordOAuth() { +func (s *Server) handlerDiscordOAuthLogin(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "login_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("discord login") +} + +func (s *Server) handlerDiscordOAuthSignup(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("discord signup") +} + +func (s *Server) handlerDiscordOAuthAdd(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("discord add") +} + +func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Request) { + + s.l.Debug("discord remove") + + user := s.getSessionUser(w, r) + + auth, err := user.GetAuthMethod(common.AUTH_DISCORD) + + if err != nil { + s.l.Info("User %s does not have Discord Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Discord Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + user, err = s.RemoveAuthMethodFromUser(auth, user) + + if err != nil { + s.l.Info("Could not remove Discord Oauth from user. %s", err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + if err != nil { + s.l.Info("Could not update user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.logout(w, r) + if err != nil { + s.l.Info("Could not logout user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { + err = s.login(user, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { + err = s.login(user, common.AUTH_PATREON, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return +} + +func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Request) { + state := r.FormValue("state") + + ok := false + for _, expectedState := range openStates { + if state == expectedState { + ok = true + } + } + if !ok { + s.l.Info("Invalid/Unknown OAuth state string: '%s'", state) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + code := r.FormValue("code") + token, err := discordOAuthConfig.Exchange(oauth2.NoContext, code) + if err != nil { + s.l.Info("Code exchange failed: %s", err) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + // Request the User data from the API + req, err := http.NewRequest("GET", "https://discord.com/api/users/@me", nil) + req.Header.Add("Authorization", "Bearer "+token.AccessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + s.l.Info("Could not retrieve Userdata from Discord API: %s", err) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + + } + if resp.StatusCode != 200 { + s.l.Error("Status Code is not 200, its %v", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + s.l.Error(err.Error()) + } + + var data map[string]interface{} + + if err := json.Unmarshal(body, &data); err != nil { + s.l.Error(err.Error()) + s.l.Debug("%v", data) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + if strings.HasPrefix(state, "signup_") { + + s.l.Debug("signup prefix") + + auth := &common.AuthMethod{ + Type: common.AUTH_DISCORD, + ExtId: data["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + + newUser := &common.User{ + Name: data["username"].(string), + Email: data["email"].(string), + NotifyCycleEnd: false, + NotifyVoteSelection: false, + } + + newUser.Id, err = s.data.AddUser(newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + newUser, err = s.AddAuthMethodToUser(auth, newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + s.l.Debug("logging in %v", newUser.Name) + s.login(newUser, common.AUTH_DISCORD, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + } else { + s.l.Debug("AuthMethod already used") + http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) + } + } else if strings.HasPrefix(state, "login_") { + s.l.Debug("login prefix") + user, err := s.data.UserDiscordLogin(data["id"].(string)) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + s.l.Debug("logging in %v", user.Name) + s.login(user, common.AUTH_DISCORD, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + } else if strings.HasPrefix(state, "add_") { + s.l.Debug("add prefix") + + user := s.getSessionUser(w, r) + + auth := &common.AuthMethod{ + Type: common.AUTH_DISCORD, + ExtId: data["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + _, err = user.GetAuthMethod(auth.Type) + if err != nil { + _, err = s.AddAuthMethodToUser(auth, user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("The provided Oauth login is already used") + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + return } func (s *Server) handlerPatreonOAuth() { diff --git a/server.go b/server.go index 2c9fa42..195687f 100644 --- a/server.go +++ b/server.go @@ -208,12 +208,18 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/login", server.handlerUserLogin) mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuthLogin) mux.HandleFunc("/user/login/twitch/callback", server.handlerTwitchOAuthCallback) + mux.HandleFunc("/user/login/discord", server.handlerDiscordOAuthLogin) + mux.HandleFunc("/user/login/discord/callback", server.handlerDiscordOAuthCallback) mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) mux.HandleFunc("/user/remove/twitch", server.handlerTwitchOAuthRemove) + mux.HandleFunc("/user/new/discord", server.handlerDiscordOAuthSignup) + mux.HandleFunc("/user/add/discord", server.handlerDiscordOAuthAdd) + mux.HandleFunc("/user/remove/discord", server.handlerDiscordOAuthRemove) + mux.HandleFunc("/user/remove/local", server.handlerLocalAuthRemove) mux.HandleFunc("/vote/", server.handlerVote) From b7c59a979a8dbbc81f4408222b1713526d053704 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Tue, 5 Jan 2021 00:53:37 +0100 Subject: [PATCH 43/67] Implemented Patreon Oauth also made use of the "HostAddress" config value to not hardcode localhost --- data/json.go | 22 +++- oauth.go | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++- server.go | 5 + 3 files changed, 335 insertions(+), 5 deletions(-) diff --git a/data/json.go b/data/json.go index 213672e..967576a 100644 --- a/data/json.go +++ b/data/json.go @@ -700,7 +700,27 @@ func (j *jsonConnector) UserTwitchLogin(extid string) (*common.User, error) { } func (j *jsonConnector) UserPatreonLogin(extid string) (*common.User, error) { - return nil, fmt.Errorf("Not implemented") + j.lock.Lock() + defer j.lock.Unlock() + + //TODO refreshing + + var id int + for _, auth := range j.AuthMethods { + if auth.ExtId == extid { + id = auth.Id + break + } + } + + for _, user := range j.Users { + for _, auth := range user.AuthMethods { + if auth == id { + return j.findUser(user.Id), nil + } + } + } + return nil, fmt.Errorf("No user found with corresponding extid") } // Get the total number of users diff --git a/oauth.go b/oauth.go index b261bc8..5d50cba 100644 --- a/oauth.go +++ b/oauth.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "net/http" + u "net/url" "strings" "github.com/zorchenhimer/MoviePolls/common" @@ -25,7 +26,17 @@ var discordEndpoint = oauth2.Endpoint{ TokenURL: "https://discord.com/api/oauth2/token", } +var patreonEndpoint = oauth2.Endpoint{ + AuthURL: "https://www.patreon.com/oauth2/authorize", + TokenURL: "https://www.patreon.com/api/oauth2/token", +} + func (s *Server) initOauth() error { + baseUrl, err := s.data.GetCfgString(ConfigHostAddress, "") + if err != nil { + return err + } + twitchOauthEnabled, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) if err != nil { return err @@ -42,7 +53,7 @@ func (s *Server) initOauth() error { } twitchOAuthConfig = &oauth2.Config{ - RedirectURL: "http://localhost:8090/user/login/twitch/callback", + RedirectURL: baseUrl + "/user/login/twitch/callback", ClientID: twitchClientID, ClientSecret: twitchClientSecret, Scopes: []string{"user:read:email"}, @@ -67,15 +78,37 @@ func (s *Server) initOauth() error { } discordOAuthConfig = &oauth2.Config{ - RedirectURL: "http://localhost:8090/user/login/discord/callback", + RedirectURL: baseUrl + "/user/login/discord/callback", ClientID: discordClientID, ClientSecret: discordClientSecret, Scopes: []string{"email", "identify"}, Endpoint: discordEndpoint, } } - // TODO cry in a corner and figure out how to do this stuff for discord and patreon + patreonOAuthEnabled, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) + if err != nil { + return err + } + + if patreonOAuthEnabled { + patreonClientID, err := s.data.GetCfgString(ConfigPatreonOauthClientID, DefaultPatreonOauthClientID) + if err != nil { + return err + } + + patreonClientSecret, err := s.data.GetCfgString(ConfigPatreonOauthClientSecret, DefaultPatreonOauthClientSecret) + if err != nil { + return err + } + patreonOAuthConfig = &oauth2.Config{ + RedirectURL: baseUrl + "/user/login/patreon/callback", + ClientID: patreonClientID, + ClientSecret: patreonClientSecret, + Scopes: []string{"identity", "identity[email]"}, + Endpoint: patreonEndpoint, + } + } return nil } @@ -693,6 +726,278 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ return } -func (s *Server) handlerPatreonOAuth() { +func (s *Server) handlerPatreonOAuthLogin(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "login_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("patreon login") +} + +func (s *Server) handlerPatreonOAuthSignup(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("patreon signup") +} + +func (s *Server) handlerPatreonOAuthAdd(w http.ResponseWriter, r *http.Request) { + // TODO that might cause impersonation attacks (i.e. using the token of an other user) + + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) + + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) + + s.l.Debug("patreon add") +} + +func (s *Server) handlerPatreonOAuthRemove(w http.ResponseWriter, r *http.Request) { + + s.l.Debug("patreon remove") + + user := s.getSessionUser(w, r) + + auth, err := user.GetAuthMethod(common.AUTH_PATREON) + + if err != nil { + s.l.Info("User %s does not have Patreon Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Patreon Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + user, err = s.RemoveAuthMethodFromUser(auth, user) + + if err != nil { + s.l.Info("Could not remove Patreon Oauth from user. %s", err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + if err != nil { + s.l.Info("Could not update user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.logout(w, r) + if err != nil { + s.l.Info("Could not logout user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { + err = s.login(user, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { + err = s.login(user, common.AUTH_DISCORD, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return +} + +func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Request) { + state := r.FormValue("state") + + ok := false + for _, expectedState := range openStates { + if state == expectedState { + ok = true + } + } + if !ok { + s.l.Info("Invalid/Unknown OAuth state string: '%s'", state) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + code := r.FormValue("code") + token, err := patreonOAuthConfig.Exchange(oauth2.NoContext, code) + if err != nil { + s.l.Info("Code exchange failed: %s", err) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + // Request the User data from the API + req, err := http.NewRequest("GET", "https://www.patreon.com/api/oauth2/v2/identity?fields"+u.QueryEscape("[user]")+"=email,first_name,full_name,last_name,vanity", nil) + req.Header.Add("Authorization", "Bearer "+token.AccessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + s.l.Info("Could not retrieve Userdata from Patreon API: %s", err) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + + } + if resp.StatusCode != 200 { + s.l.Error("Status Code is not 200, its %v", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + s.l.Error(err.Error()) + } + + var data map[string]interface{} + + if err := json.Unmarshal(body, &data); err != nil { + s.l.Error(err.Error()) + s.l.Debug("%v", data) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + data = data["data"].(map[string]interface{}) + + if strings.HasPrefix(state, "signup_") { + + s.l.Debug("signup prefix") + + auth := &common.AuthMethod{ + Type: common.AUTH_PATREON, + ExtId: data["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + + newUser := &common.User{ + Name: data["attributes"].(map[string]interface{})["full_name"].(string), + Email: data["attributes"].(map[string]interface{})["email"].(string), + NotifyCycleEnd: false, + NotifyVoteSelection: false, + } + + newUser.Id, err = s.data.AddUser(newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + newUser, err = s.AddAuthMethodToUser(auth, newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(newUser) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + + s.l.Debug("logging in %v", newUser.Name) + s.login(newUser, common.AUTH_PATREON, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + } else { + s.l.Debug("AuthMethod already used") + http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) + } + } else if strings.HasPrefix(state, "login_") { + s.l.Debug("login prefix") + user, err := s.data.UserPatreonLogin(data["id"].(string)) + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return + } + s.l.Debug("logging in %v", user.Name) + s.login(user, common.AUTH_PATREON, w, r) + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + } else if strings.HasPrefix(state, "add_") { + s.l.Debug("add prefix") + + user := s.getSessionUser(w, r) + + auth := &common.AuthMethod{ + Type: common.AUTH_PATREON, + ExtId: data["id"].(string), + AuthToken: token.AccessToken, + RefreshToken: token.RefreshToken, + RefreshDate: token.Expiry, + } + + if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + _, err = user.GetAuthMethod(auth.Type) + if err != nil { + _, err = s.AddAuthMethodToUser(auth, user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + + err = s.data.UpdateUser(user) + + if err != nil { + s.l.Error(err.Error()) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else { + s.l.Error("The provided Oauth login is already used") + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + return } diff --git a/server.go b/server.go index 195687f..d6a7174 100644 --- a/server.go +++ b/server.go @@ -210,6 +210,8 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/login/twitch/callback", server.handlerTwitchOAuthCallback) mux.HandleFunc("/user/login/discord", server.handlerDiscordOAuthLogin) mux.HandleFunc("/user/login/discord/callback", server.handlerDiscordOAuthCallback) + mux.HandleFunc("/user/login/patreon", server.handlerPatreonOAuthLogin) + mux.HandleFunc("/user/login/patreon/callback", server.handlerPatreonOAuthCallback) mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) @@ -219,6 +221,9 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user/new/discord", server.handlerDiscordOAuthSignup) mux.HandleFunc("/user/add/discord", server.handlerDiscordOAuthAdd) mux.HandleFunc("/user/remove/discord", server.handlerDiscordOAuthRemove) + mux.HandleFunc("/user/new/patreon", server.handlerPatreonOAuthSignup) + mux.HandleFunc("/user/add/patreon", server.handlerPatreonOAuthAdd) + mux.HandleFunc("/user/remove/patreon", server.handlerPatreonOAuthRemove) mux.HandleFunc("/user/remove/local", server.handlerLocalAuthRemove) From f750ad36a9bad0007d0d0e4c875c4b4801ba81d5 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Tue, 5 Jan 2021 01:01:01 +0100 Subject: [PATCH 44/67] Fixed the signup page saying login instead --- templates/newaccount.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/newaccount.html b/templates/newaccount.html index 65d10ef..06c3f8c 100644 --- a/templates/newaccount.html +++ b/templates/newaccount.html @@ -25,13 +25,13 @@ {{if .OAuth}}
    {{if .TwitchOAuth}} - Login with Twitch + Signup with Twitch {{end}} {{if .DiscordOAuth}} - Login with Discord + Signup with Discord {{end}} {{if .PatreonOAuth}} - Login with Patreon + Signup with Patreon {{end}}
    {{end}} From f10116c9d036e4d1d5397e60f5ccd75ad2e38237 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Tue, 5 Jan 2021 02:24:56 +0100 Subject: [PATCH 45/67] Reordered handlers --- server.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/server.go b/server.go index d6a7174..493c374 100644 --- a/server.go +++ b/server.go @@ -206,25 +206,27 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user", server.handlerUser) mux.HandleFunc("/user/login", server.handlerUserLogin) - mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuthLogin) - mux.HandleFunc("/user/login/twitch/callback", server.handlerTwitchOAuthCallback) - mux.HandleFunc("/user/login/discord", server.handlerDiscordOAuthLogin) - mux.HandleFunc("/user/login/discord/callback", server.handlerDiscordOAuthCallback) - mux.HandleFunc("/user/login/patreon", server.handlerPatreonOAuthLogin) - mux.HandleFunc("/user/login/patreon/callback", server.handlerPatreonOAuthCallback) - mux.HandleFunc("/user/logout", server.handlerUserLogout) - mux.HandleFunc("/user/new", server.handlerUserNew) + mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuthLogin) mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) mux.HandleFunc("/user/remove/twitch", server.handlerTwitchOAuthRemove) + mux.HandleFunc("/user/callback/twitch", server.handlerTwitchOAuthCallback) + + mux.HandleFunc("/user/login/discord", server.handlerDiscordOAuthLogin) mux.HandleFunc("/user/new/discord", server.handlerDiscordOAuthSignup) mux.HandleFunc("/user/add/discord", server.handlerDiscordOAuthAdd) mux.HandleFunc("/user/remove/discord", server.handlerDiscordOAuthRemove) + mux.HandleFunc("/user/callback/discord", server.handlerDiscordOAuthCallback) + + mux.HandleFunc("/user/login/patreon", server.handlerPatreonOAuthLogin) mux.HandleFunc("/user/new/patreon", server.handlerPatreonOAuthSignup) mux.HandleFunc("/user/add/patreon", server.handlerPatreonOAuthAdd) mux.HandleFunc("/user/remove/patreon", server.handlerPatreonOAuthRemove) + mux.HandleFunc("/user/callback/patreon", server.handlerPatreonOAuthCallback) + mux.HandleFunc("/user/logout", server.handlerUserLogout) + mux.HandleFunc("/user/new", server.handlerUserNew) mux.HandleFunc("/user/remove/local", server.handlerLocalAuthRemove) mux.HandleFunc("/vote/", server.handlerVote) From cb696ec0cab2ec8bb2e0115ef491849321c83abe Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Tue, 5 Jan 2021 02:25:25 +0100 Subject: [PATCH 46/67] Cleanup, logging improvements, comments --- oauth.go | 154 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 64 deletions(-) diff --git a/oauth.go b/oauth.go index 5d50cba..3acf8e2 100644 --- a/oauth.go +++ b/oauth.go @@ -31,6 +31,8 @@ var patreonEndpoint = oauth2.Endpoint{ TokenURL: "https://www.patreon.com/api/oauth2/token", } +// Initiate the OAuth configs, this includes loading the ConfigValues into "memory" to be used in the login methods +// Returns: Error if a config value could not be retrieved func (s *Server) initOauth() error { baseUrl, err := s.data.GetCfgString(ConfigHostAddress, "") if err != nil { @@ -53,7 +55,7 @@ func (s *Server) initOauth() error { } twitchOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/login/twitch/callback", + RedirectURL: baseUrl + "/user/callback/twitch", ClientID: twitchClientID, ClientSecret: twitchClientSecret, Scopes: []string{"user:read:email"}, @@ -78,7 +80,7 @@ func (s *Server) initOauth() error { } discordOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/login/discord/callback", + RedirectURL: baseUrl + "/user/callback/discord", ClientID: discordClientID, ClientSecret: discordClientSecret, Scopes: []string{"email", "identify"}, @@ -102,7 +104,7 @@ func (s *Server) initOauth() error { } patreonOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/login/patreon/callback", + RedirectURL: baseUrl + "/user/callback/patreon", ClientID: patreonClientID, ClientSecret: patreonClientSecret, Scopes: []string{"identity", "identity[email]"}, @@ -112,6 +114,7 @@ func (s *Server) initOauth() error { return nil } +// Removes the AuthType LOCAL AuthMethod from the currently logged in user func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) { s.l.Debug("local remove") @@ -146,6 +149,7 @@ func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) return } + // Logging the user out to ensure that he is logged in with an existing AuthMethod err = s.logout(w, r) if err != nil { s.l.Info("Could not logout user %s", user.Name) @@ -153,6 +157,7 @@ func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) return } + // Logging the user back in if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { err = s.login(user, common.AUTH_TWITCH, w, r) if err != nil { @@ -179,6 +184,7 @@ func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) return } +// Creates an OAuth request to log the user in using the TWITCH OAuth func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -193,6 +199,8 @@ func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) s.l.Debug("twitch login") } +// Creates an OAuth request to sign up the user using the TWITCH OAuth +// A new user is created and a new AuthMethod struct is created and associated func (s *Server) handlerTwitchOAuthSignup(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -204,9 +212,10 @@ func (s *Server) handlerTwitchOAuthSignup(w http.ResponseWriter, r *http.Request url := twitchOAuthConfig.AuthCodeURL(oauthStateString) http.Redirect(w, r, url, http.StatusTemporaryRedirect) - s.l.Debug("twitch signup") + s.l.Debug("twitch sign up") } +// Creates an OAuth request to add a new Twitch AuthMethod to the currently logged in user func (s *Server) handlerTwitchOAuthAdd(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -221,8 +230,8 @@ func (s *Server) handlerTwitchOAuthAdd(w http.ResponseWriter, r *http.Request) { s.l.Debug("twitch add") } +// Removes the Twitch AuthMethod from the currently logged in user func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request) { - s.l.Debug("twitch remove") user := s.getSessionUser(w, r) @@ -255,6 +264,7 @@ func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request return } + // Log the user out to ensure he uses an existing AuthMethod err = s.logout(w, r) if err != nil { s.l.Info("Could not logout user %s", user.Name) @@ -262,6 +272,7 @@ func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request return } + // Find a new AuthMethod to log the user back in if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { err = s.login(user, common.AUTH_LOCAL, w, r) if err != nil { @@ -284,10 +295,14 @@ func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request return } } + + s.l.Debug("twitch remove") + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } +// This function handles all Twitch Callbacks (add/signup/login) func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") @@ -324,28 +339,30 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } + if resp.StatusCode != 200 { - s.l.Error("Status Code is not 200, its %v", resp.Status) + s.l.Info("Status Code is not 200, its %v", resp.Status) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } body, err := ioutil.ReadAll(resp.Body) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } var data map[string][]map[string]interface{} if err := json.Unmarshal(body, &data); err != nil { - s.l.Error(err.Error()) - s.l.Debug("%v", data) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } if strings.HasPrefix(state, "signup_") { - - s.l.Debug("signup prefix") - + // Handle the sign up process auth := &common.AuthMethod{ Type: common.AUTH_TWITCH, ExtId: data["data"][0]["id"].(string), @@ -354,8 +371,9 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque RefreshDate: token.Expiry, } + // check if Twitch Auth is already used if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { - + // Create a new user newUser := &common.User{ Name: data["data"][0]["display_name"].(string), Email: data["data"][0]["email"].(string), @@ -363,26 +381,29 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque NotifyVoteSelection: false, } + // add this new server to the database newUser.Id, err = s.data.AddUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } + // add the authmethod to the user newUser, err = s.AddAuthMethodToUser(auth, newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } + // update the user in the DB with the user having the AuthMethod associated err = s.data.UpdateUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -395,10 +416,10 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) } } else if strings.HasPrefix(state, "login_") { - s.l.Debug("login prefix") + // Handle Twitch Login user, err := s.data.UserTwitchLogin(data["data"][0]["id"].(string)) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -406,8 +427,9 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque s.login(user, common.AUTH_TWITCH, w, r) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else if strings.HasPrefix(state, "add_") { - s.l.Debug("add prefix") + // Handle adding a Twitch AuthMethod to the logged in user + // get the current user user := s.getSessionUser(w, r) auth := &common.AuthMethod{ @@ -418,13 +440,15 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque RefreshDate: token.Expiry, } + // check if this oauth is already used if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { + // check if the user already has an other Twitch OAuth connected _, err = user.GetAuthMethod(auth.Type) if err != nil { _, err = s.AddAuthMethodToUser(auth, user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -432,17 +456,17 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque err = s.data.UpdateUser(user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) + s.l.Info("User %s already has %s Oauth associated", user.Name, auth.Type) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("The provided Oauth login is already used") + s.l.Info("The provided Oauth login is already used") http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -452,6 +476,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } +// Creates an OAuth query to log a user in using Discord OAuth func (s *Server) handlerDiscordOAuthLogin(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -466,6 +491,7 @@ func (s *Server) handlerDiscordOAuthLogin(w http.ResponseWriter, r *http.Request s.l.Debug("discord login") } +// Creates an OAuth query to sign up a user using Discord OAuth func (s *Server) handlerDiscordOAuthSignup(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -480,6 +506,7 @@ func (s *Server) handlerDiscordOAuthSignup(w http.ResponseWriter, r *http.Reques s.l.Debug("discord signup") } +// Creates an OAuth query to add an Discord AuthMethod to the currently logged in user func (s *Server) handlerDiscordOAuthAdd(w http.ResponseWriter, r *http.Request) { // TODO that might cause impersonation attacks (i.e. using the token of an other user) @@ -494,10 +521,9 @@ func (s *Server) handlerDiscordOAuthAdd(w http.ResponseWriter, r *http.Request) s.l.Debug("discord add") } +// Removes the Discord AuthMethod from the currently logged in user func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Request) { - s.l.Debug("discord remove") - user := s.getSessionUser(w, r) auth, err := user.GetAuthMethod(common.AUTH_DISCORD) @@ -529,6 +555,7 @@ func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Reques return } + // Log the user out to ensure he is logged in with an existing AuthMethod err = s.logout(w, r) if err != nil { s.l.Info("Could not logout user %s", user.Name) @@ -536,6 +563,7 @@ func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Reques return } + // Try to log the user back in if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { err = s.login(user, common.AUTH_TWITCH, w, r) if err != nil { @@ -559,10 +587,13 @@ func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Reques } } + s.l.Debug("discord remove") + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } +// Handler for the Discord OAuth Callbacks (add/signup/login) func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") @@ -596,30 +627,31 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ s.l.Info("Could not retrieve Userdata from Discord API: %s", err) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return - } + if resp.StatusCode != 200 { - s.l.Error("Status Code is not 200, its %v", resp.Status) + s.l.Info("Status Code is not 200, its %v", resp.Status) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } body, err := ioutil.ReadAll(resp.Body) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } var data map[string]interface{} if err := json.Unmarshal(body, &data); err != nil { - s.l.Error(err.Error()) - s.l.Debug("%v", data) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } if strings.HasPrefix(state, "signup_") { - s.l.Debug("signup prefix") - auth := &common.AuthMethod{ Type: common.AUTH_DISCORD, ExtId: data["id"].(string), @@ -629,7 +661,6 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { - newUser := &common.User{ Name: data["username"].(string), Email: data["email"].(string), @@ -640,7 +671,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ newUser.Id, err = s.data.AddUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -648,7 +679,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ newUser, err = s.AddAuthMethodToUser(auth, newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -656,7 +687,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ err = s.data.UpdateUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -669,10 +700,9 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) } } else if strings.HasPrefix(state, "login_") { - s.l.Debug("login prefix") user, err := s.data.UserDiscordLogin(data["id"].(string)) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -680,8 +710,6 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ s.login(user, common.AUTH_DISCORD, w, r) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else if strings.HasPrefix(state, "add_") { - s.l.Debug("add prefix") - user := s.getSessionUser(w, r) auth := &common.AuthMethod{ @@ -698,7 +726,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ _, err = s.AddAuthMethodToUser(auth, user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -706,17 +734,17 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ err = s.data.UpdateUser(user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) + s.l.Info("User %s already has %s Oauth associated", user.Name, auth.Type) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("The provided Oauth login is already used") + s.l.Info("The provided Oauth login is already used") http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -770,8 +798,6 @@ func (s *Server) handlerPatreonOAuthAdd(w http.ResponseWriter, r *http.Request) func (s *Server) handlerPatreonOAuthRemove(w http.ResponseWriter, r *http.Request) { - s.l.Debug("patreon remove") - user := s.getSessionUser(w, r) auth, err := user.GetAuthMethod(common.AUTH_PATREON) @@ -833,6 +859,8 @@ func (s *Server) handlerPatreonOAuthRemove(w http.ResponseWriter, r *http.Reques } } + s.l.Debug("patreon remove") + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -873,19 +901,22 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ } if resp.StatusCode != 200 { - s.l.Error("Status Code is not 200, its %v", resp.Status) + s.l.Info("Status Code is not 200, its %v", resp.Status) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } body, err := ioutil.ReadAll(resp.Body) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + return } var data map[string]interface{} if err := json.Unmarshal(body, &data); err != nil { - s.l.Error(err.Error()) - s.l.Debug("%v", data) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -894,8 +925,6 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ if strings.HasPrefix(state, "signup_") { - s.l.Debug("signup prefix") - auth := &common.AuthMethod{ Type: common.AUTH_PATREON, ExtId: data["id"].(string), @@ -916,7 +945,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ newUser.Id, err = s.data.AddUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -924,7 +953,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ newUser, err = s.AddAuthMethodToUser(auth, newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -932,7 +961,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ err = s.data.UpdateUser(newUser) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -941,14 +970,13 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ s.login(newUser, common.AUTH_PATREON, w, r) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else { - s.l.Debug("AuthMethod already used") + s.l.Info("AuthMethod already used") http.Redirect(w, r, "/user/new", http.StatusTemporaryRedirect) } } else if strings.HasPrefix(state, "login_") { - s.l.Debug("login prefix") user, err := s.data.UserPatreonLogin(data["id"].(string)) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) return } @@ -956,8 +984,6 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ s.login(user, common.AUTH_PATREON, w, r) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else if strings.HasPrefix(state, "add_") { - s.l.Debug("add prefix") - user := s.getSessionUser(w, r) auth := &common.AuthMethod{ @@ -974,7 +1000,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ _, err = s.AddAuthMethodToUser(auth, user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } @@ -982,17 +1008,17 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ err = s.data.UpdateUser(user) if err != nil { - s.l.Error(err.Error()) + s.l.Info(err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("User %s already has %s Oauth associated", user.Name, auth.Type) + s.l.Info("User %s already has %s Oauth associated", user.Name, auth.Type) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } } else { - s.l.Error("The provided Oauth login is already used") + s.l.Info("The provided Oauth login is already used") http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } From 925ef6b178cbd84df85f82fdeabd4510228ab1da Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 6 Jan 2021 20:49:24 +0100 Subject: [PATCH 47/67] Added oauth reload after changing the configuration --- admin.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/admin.go b/admin.go index 7e4783d..76f76e6 100644 --- a/admin.go +++ b/admin.go @@ -504,6 +504,13 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { // Set this down here so the Notice Banner is updated data.dataPageBase = s.newPageBase("Admin - Config", w, r) + // Reload Oauth + err = s.initOauth() + + if err != nil { + data.ErrorMessage = append(data.ErrorMessage, err.Error()) + } + if err := s.executeTemplate(w, "adminConfig", data); err != nil { s.l.Error("Error rendering template: %v", err) } From 332903087434473d85b57c074007cde1184c8efa Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 6 Jan 2021 20:50:10 +0100 Subject: [PATCH 48/67] Added some hardening against empty config fields --- oauth.go | 56 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/oauth.go b/oauth.go index 3acf8e2..c093c3b 100644 --- a/oauth.go +++ b/oauth.go @@ -2,6 +2,7 @@ package moviepoll import ( "encoding/json" + "fmt" "io/ioutil" "net/http" u "net/url" @@ -34,26 +35,51 @@ var patreonEndpoint = oauth2.Endpoint{ // Initiate the OAuth configs, this includes loading the ConfigValues into "memory" to be used in the login methods // Returns: Error if a config value could not be retrieved func (s *Server) initOauth() error { - baseUrl, err := s.data.GetCfgString(ConfigHostAddress, "") + + twitchOauthEnabled, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) if err != nil { return err } - twitchOauthEnabled, err := s.data.GetCfgBool(ConfigTwitchOauthEnabled, DefaultTwitchOauthEnabled) + discordOAuthEnabled, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) + if err != nil { + return err + } + + patreonOAuthEnabled, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) if err != nil { return err } + + baseUrl, err := s.data.GetCfgString(ConfigHostAddress, "") + if err != nil { + return err + } + + if twitchOauthEnabled || discordOAuthEnabled || patreonOAuthEnabled { + if baseUrl == "" { + return fmt.Errorf("Config Value for HostAddress cannot be empty to use OAuth") + } + } + if twitchOauthEnabled { twitchClientID, err := s.data.GetCfgString(ConfigTwitchOauthClientID, DefaultTwitchOauthClientID) if err != nil { return err } + if twitchClientID == "" { + return fmt.Errorf("Config Value for TwitchOauthClientID cannot be empty to use OAuth") + } twitchClientSecret, err := s.data.GetCfgString(ConfigTwitchOauthClientSecret, DefaultTwitchOauthClientSecret) if err != nil { return err } + if twitchClientSecret == "" { + return fmt.Errorf("Config Value for TwitchOauthClientSecret cannot be empty to use OAuth") + } + twitchOAuthConfig = &oauth2.Config{ RedirectURL: baseUrl + "/user/callback/twitch", ClientID: twitchClientID, @@ -63,22 +89,25 @@ func (s *Server) initOauth() error { } } - discordOAuthEnabled, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) - if err != nil { - return err - } - if discordOAuthEnabled { discordClientID, err := s.data.GetCfgString(ConfigDiscordOauthClientID, DefaultDiscordOauthClientID) if err != nil { return err } + if discordClientID == "" { + return fmt.Errorf("Config Value for DiscordOauthClientID cannot be empty to use OAuth") + } + discordClientSecret, err := s.data.GetCfgString(ConfigDiscordOauthClientSecret, DefaultDiscordOauthClientSecret) if err != nil { return err } + if discordClientSecret == "" { + return fmt.Errorf("Config Value for DiscordOauthClientSecret cannot be empty to use OAuth") + } + discordOAuthConfig = &oauth2.Config{ RedirectURL: baseUrl + "/user/callback/discord", ClientID: discordClientID, @@ -87,10 +116,6 @@ func (s *Server) initOauth() error { Endpoint: discordEndpoint, } } - patreonOAuthEnabled, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) - if err != nil { - return err - } if patreonOAuthEnabled { patreonClientID, err := s.data.GetCfgString(ConfigPatreonOauthClientID, DefaultPatreonOauthClientID) @@ -98,11 +123,19 @@ func (s *Server) initOauth() error { return err } + if patreonClientID == "" { + return fmt.Errorf("Config Value for PatreonOauthClientSecret cannot be empty to use OAuth") + } + patreonClientSecret, err := s.data.GetCfgString(ConfigPatreonOauthClientSecret, DefaultPatreonOauthClientSecret) if err != nil { return err } + if patreonClientSecret == "" { + return fmt.Errorf("Config Value for PatreonOauthClientSecret cannot be empty to use OAuth") + } + patreonOAuthConfig = &oauth2.Config{ RedirectURL: baseUrl + "/user/callback/patreon", ClientID: patreonClientID, @@ -111,6 +144,7 @@ func (s *Server) initOauth() error { Endpoint: patreonEndpoint, } } + return nil } From a0062f1cd07f4f59c8e72ae3a3012ada7f64e4b3 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 6 Jan 2021 22:53:26 +0100 Subject: [PATCH 49/67] Reworked Oauth flow to make use of query parameters Also changed around some paths for the endpoints --- oauth.go | 487 ++++++++++++++++++------------------- server.go | 25 +- templates/account.html | 12 +- templates/newaccount.html | 6 +- templates/plain-login.html | 6 +- 5 files changed, 252 insertions(+), 284 deletions(-) diff --git a/oauth.go b/oauth.go index c093c3b..2dbdb2a 100644 --- a/oauth.go +++ b/oauth.go @@ -81,7 +81,7 @@ func (s *Server) initOauth() error { } twitchOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/callback/twitch", + RedirectURL: baseUrl + "/oauth/twitch/callback", ClientID: twitchClientID, ClientSecret: twitchClientSecret, Scopes: []string{"user:read:email"}, @@ -109,7 +109,7 @@ func (s *Server) initOauth() error { } discordOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/callback/discord", + RedirectURL: baseUrl + "/oauth/discord/callback", ClientID: discordClientID, ClientSecret: discordClientSecret, Scopes: []string{"email", "identify"}, @@ -137,7 +137,7 @@ func (s *Server) initOauth() error { } patreonOAuthConfig = &oauth2.Config{ - RedirectURL: baseUrl + "/user/callback/patreon", + RedirectURL: baseUrl + "/oauth/patreon/callback", ClientID: patreonClientID, ClientSecret: patreonClientSecret, Scopes: []string{"identity", "identity[email]"}, @@ -218,122 +218,112 @@ func (s *Server) handlerLocalAuthRemove(w http.ResponseWriter, r *http.Request) return } -// Creates an OAuth request to log the user in using the TWITCH OAuth -func (s *Server) handlerTwitchOAuthLogin(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) +func (s *Server) handlerTwitchOAuth(w http.ResponseWriter, r *http.Request) { + action := r.URL.Query().Get("action") - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "login_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + switch action { + case "login": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "login_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Handle the Oauth redirect - url := twitchOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + // Handle the Oauth redirect + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - s.l.Debug("twitch login") -} - -// Creates an OAuth request to sign up the user using the TWITCH OAuth -// A new user is created and a new AuthMethod struct is created and associated -func (s *Server) handlerTwitchOAuthSignup(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) - - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "signup_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) - - // Handle the Oauth redirect - url := twitchOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) - - s.l.Debug("twitch sign up") -} - -// Creates an OAuth request to add a new Twitch AuthMethod to the currently logged in user -func (s *Server) handlerTwitchOAuthAdd(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) + s.l.Debug("twitch login") - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "add_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + case "signup": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Handle the Oauth redirect - url := twitchOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + // Handle the Oauth redirect + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - s.l.Debug("twitch add") -} - -// Removes the Twitch AuthMethod from the currently logged in user -func (s *Server) handlerTwitchOAuthRemove(w http.ResponseWriter, r *http.Request) { + s.l.Debug("twitch sign up") - user := s.getSessionUser(w, r) + case "add": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - auth, err := user.GetAuthMethod(common.AUTH_TWITCH) + // Handle the Oauth redirect + url := twitchOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - if err != nil { - s.l.Info("User %s does not have Twitch Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + s.l.Debug("twitch add") - if len(user.AuthMethods) == 1 { - s.l.Info("User %v only has Twitch Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + case "remove": + user := s.getSessionUser(w, r) - user, err = s.RemoveAuthMethodFromUser(auth, user) + auth, err := user.GetAuthMethod(common.AUTH_TWITCH) - if err != nil { - s.l.Info("Could not remove Twitch Oauth from user. %s", err.Error()) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + if err != nil { + s.l.Info("User %s does not have Twitch Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - err = s.data.UpdateUser(user) - if err != nil { - s.l.Info("Could not update user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Twitch Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - // Log the user out to ensure he uses an existing AuthMethod - err = s.logout(w, r) - if err != nil { - s.l.Info("Could not logout user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + user, err = s.RemoveAuthMethodFromUser(auth, user) - // Find a new AuthMethod to log the user back in - if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { - err = s.login(user, common.AUTH_LOCAL, w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not remove Twitch Oauth from user. %s", err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { - err = s.login(user, common.AUTH_DISCORD, w, r) + + err = s.data.UpdateUser(user) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not update user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { - err = s.login(user, common.AUTH_PATREON, w, r) + + // Log the user out to ensure he uses an existing AuthMethod + err = s.logout(w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not logout user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } - s.l.Debug("twitch remove") + // Find a new AuthMethod to log the user back in + if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { + err = s.login(user, common.AUTH_DISCORD, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { + err = s.login(user, common.AUTH_PATREON, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + + s.l.Debug("twitch remove") - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } } // This function handles all Twitch Callbacks (add/signup/login) @@ -510,121 +500,113 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque return } -// Creates an OAuth query to log a user in using Discord OAuth -func (s *Server) handlerDiscordOAuthLogin(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) +func (s *Server) handlerDiscordOAuth(w http.ResponseWriter, r *http.Request) { - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "login_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + action := r.URL.Query().Get("action") - // Handle the Oauth redirect - url := discordOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + switch action { + case "login": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "login_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - s.l.Debug("discord login") -} + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) -// Creates an OAuth query to sign up a user using Discord OAuth -func (s *Server) handlerDiscordOAuthSignup(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) + s.l.Debug("discord login") - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "signup_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + case "signup": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Handle the Oauth redirect - url := discordOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - s.l.Debug("discord signup") -} + s.l.Debug("discord signup") -// Creates an OAuth query to add an Discord AuthMethod to the currently logged in user -func (s *Server) handlerDiscordOAuthAdd(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) + case "add": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "add_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + // Handle the Oauth redirect + url := discordOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - // Handle the Oauth redirect - url := discordOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + s.l.Debug("discord add") - s.l.Debug("discord add") -} - -// Removes the Discord AuthMethod from the currently logged in user -func (s *Server) handlerDiscordOAuthRemove(w http.ResponseWriter, r *http.Request) { - - user := s.getSessionUser(w, r) - - auth, err := user.GetAuthMethod(common.AUTH_DISCORD) - - if err != nil { - s.l.Info("User %s does not have Discord Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + case "remove": + user := s.getSessionUser(w, r) - if len(user.AuthMethods) == 1 { - s.l.Info("User %v only has Discord Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + auth, err := user.GetAuthMethod(common.AUTH_DISCORD) - user, err = s.RemoveAuthMethodFromUser(auth, user) + if err != nil { + s.l.Info("User %s does not have Discord Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - if err != nil { - s.l.Info("Could not remove Discord Oauth from user. %s", err.Error()) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Discord Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - err = s.data.UpdateUser(user) - if err != nil { - s.l.Info("Could not update user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + user, err = s.RemoveAuthMethodFromUser(auth, user) - // Log the user out to ensure he is logged in with an existing AuthMethod - err = s.logout(w, r) - if err != nil { - s.l.Info("Could not logout user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } - - // Try to log the user back in - if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { - err = s.login(user, common.AUTH_TWITCH, w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not remove Discord Oauth from user. %s", err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { - err = s.login(user, common.AUTH_LOCAL, w, r) + + err = s.data.UpdateUser(user) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not update user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { - err = s.login(user, common.AUTH_PATREON, w, r) + + // Log the user out to ensure he is logged in with an existing AuthMethod + err = s.logout(w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not logout user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } - s.l.Debug("discord remove") + // Try to log the user back in + if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { + err = s.login(user, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_PATREON); err == nil { + err = s.login(user, common.AUTH_PATREON, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + + s.l.Debug("discord remove") - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } } // Handler for the Discord OAuth Callbacks (add/signup/login) @@ -788,115 +770,110 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ return } -func (s *Server) handlerPatreonOAuthLogin(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) - - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "login_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) +func (s *Server) handlerPatreonOAuth(w http.ResponseWriter, r *http.Request) { + action := r.URL.Query().Get("action") - // Handle the Oauth redirect - url := patreonOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + switch action { + case "login": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "login_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - s.l.Debug("patreon login") -} + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) -func (s *Server) handlerPatreonOAuthSignup(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) + s.l.Debug("patreon login") - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "signup_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) + case "signup": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "signup_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Handle the Oauth redirect - url := patreonOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) - s.l.Debug("patreon signup") -} + s.l.Debug("patreon signup") -func (s *Server) handlerPatreonOAuthAdd(w http.ResponseWriter, r *http.Request) { - // TODO that might cause impersonation attacks (i.e. using the token of an other user) + case "add": + // Generate a new state string for each login attempt and store it in the state list + oauthStateString := "add_" + getCryptRandKey(32) + openStates = append(openStates, oauthStateString) - // Generate a new state string for each login attempt and store it in the state list - oauthStateString := "add_" + getCryptRandKey(32) - openStates = append(openStates, oauthStateString) - - // Handle the Oauth redirect - url := patreonOAuthConfig.AuthCodeURL(oauthStateString) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) - - s.l.Debug("patreon add") -} + // Handle the Oauth redirect + url := patreonOAuthConfig.AuthCodeURL(oauthStateString) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) -func (s *Server) handlerPatreonOAuthRemove(w http.ResponseWriter, r *http.Request) { + s.l.Debug("patreon add") - user := s.getSessionUser(w, r) - - auth, err := user.GetAuthMethod(common.AUTH_PATREON) - - if err != nil { - s.l.Info("User %s does not have Patreon Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + case "remove": + user := s.getSessionUser(w, r) - if len(user.AuthMethods) == 1 { - s.l.Info("User %v only has Patreon Oauth associated with him", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + auth, err := user.GetAuthMethod(common.AUTH_PATREON) - user, err = s.RemoveAuthMethodFromUser(auth, user) - - if err != nil { - s.l.Info("Could not remove Patreon Oauth from user. %s", err.Error()) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + if err != nil { + s.l.Info("User %s does not have Patreon Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - err = s.data.UpdateUser(user) - if err != nil { - s.l.Info("Could not update user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + if len(user.AuthMethods) == 1 { + s.l.Info("User %v only has Patreon Oauth associated with him", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } - err = s.logout(w, r) - if err != nil { - s.l.Info("Could not logout user %s", user.Name) - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return - } + user, err = s.RemoveAuthMethodFromUser(auth, user) - if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { - err = s.login(user, common.AUTH_TWITCH, w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not remove Patreon Oauth from user. %s", err.Error()) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { - err = s.login(user, common.AUTH_DISCORD, w, r) + + err = s.data.UpdateUser(user) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not update user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { - err = s.login(user, common.AUTH_LOCAL, w, r) + + err = s.logout(w, r) if err != nil { - s.l.Info("Could not login user %s", user.Name) + s.l.Info("Could not logout user %s", user.Name) http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) return } - } - s.l.Debug("patreon remove") + if _, err := user.GetAuthMethod(common.AUTH_TWITCH); err == nil { + err = s.login(user, common.AUTH_TWITCH, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_DISCORD); err == nil { + err = s.login(user, common.AUTH_DISCORD, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } else if _, err := user.GetAuthMethod(common.AUTH_LOCAL); err == nil { + err = s.login(user, common.AUTH_LOCAL, w, r) + if err != nil { + s.l.Info("Could not login user %s", user.Name) + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } + } + + s.l.Debug("patreon remove") - http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) - return + http.Redirect(w, r, "/user", http.StatusTemporaryRedirect) + return + } } func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Request) { diff --git a/server.go b/server.go index 493c374..dd0f7c7 100644 --- a/server.go +++ b/server.go @@ -207,23 +207,14 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/user", server.handlerUser) mux.HandleFunc("/user/login", server.handlerUserLogin) - mux.HandleFunc("/user/login/twitch", server.handlerTwitchOAuthLogin) - mux.HandleFunc("/user/new/twitch", server.handlerTwitchOAuthSignup) - mux.HandleFunc("/user/add/twitch", server.handlerTwitchOAuthAdd) - mux.HandleFunc("/user/remove/twitch", server.handlerTwitchOAuthRemove) - mux.HandleFunc("/user/callback/twitch", server.handlerTwitchOAuthCallback) - - mux.HandleFunc("/user/login/discord", server.handlerDiscordOAuthLogin) - mux.HandleFunc("/user/new/discord", server.handlerDiscordOAuthSignup) - mux.HandleFunc("/user/add/discord", server.handlerDiscordOAuthAdd) - mux.HandleFunc("/user/remove/discord", server.handlerDiscordOAuthRemove) - mux.HandleFunc("/user/callback/discord", server.handlerDiscordOAuthCallback) - - mux.HandleFunc("/user/login/patreon", server.handlerPatreonOAuthLogin) - mux.HandleFunc("/user/new/patreon", server.handlerPatreonOAuthSignup) - mux.HandleFunc("/user/add/patreon", server.handlerPatreonOAuthAdd) - mux.HandleFunc("/user/remove/patreon", server.handlerPatreonOAuthRemove) - mux.HandleFunc("/user/callback/patreon", server.handlerPatreonOAuthCallback) + mux.HandleFunc("/oauth/twitch", server.handlerTwitchOAuth) + mux.HandleFunc("/oauth/twitch/callback", server.handlerTwitchOAuthCallback) + + mux.HandleFunc("/oauth/discord", server.handlerDiscordOAuth) + mux.HandleFunc("/oauth/discord/callback", server.handlerDiscordOAuthCallback) + + mux.HandleFunc("/oauth/patreon", server.handlerPatreonOAuth) + mux.HandleFunc("/oauth/patreon/callback", server.handlerPatreonOAuthCallback) mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) diff --git a/templates/account.html b/templates/account.html index 8a126f6..6e50756 100644 --- a/templates/account.html +++ b/templates/account.html @@ -76,9 +76,9 @@ {{if .TwitchOAuthEnabled}} {{ end }} @@ -87,9 +87,9 @@ {{if .DiscordOAuthEnabled}} {{end}} @@ -98,9 +98,9 @@ {{if .PatreonOAuthEnabled}} {{end}} diff --git a/templates/newaccount.html b/templates/newaccount.html index 06c3f8c..595407d 100644 --- a/templates/newaccount.html +++ b/templates/newaccount.html @@ -25,13 +25,13 @@ {{if .OAuth}}
    {{if .TwitchOAuth}} - Signup with Twitch + Signup with Twitch {{end}} {{if .DiscordOAuth}} - Signup with Discord + Signup with Discord {{end}} {{if .PatreonOAuth}} - Signup with Patreon + Signup with Patreon {{end}}
    {{end}} diff --git a/templates/plain-login.html b/templates/plain-login.html index a1a2057..18f5447 100644 --- a/templates/plain-login.html +++ b/templates/plain-login.html @@ -28,13 +28,13 @@ {{if .OAuth}}
    {{if .TwitchOAuth}} - Login with Twitch + Login with Twitch {{end}} {{if .DiscordOAuth}} - Login with Discord + Login with Discord {{end}} {{if .PatreonOAuth}} - Login with Patreon + Login with Patreon {{end}}
    {{end}} From f7727b4c286ef486498212b93e88a36e332db8a7 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Wed, 6 Jan 2021 22:56:10 +0100 Subject: [PATCH 50/67] Moved handlers around (no functional changes) --- server.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index dd0f7c7..e331e35 100644 --- a/server.go +++ b/server.go @@ -204,9 +204,6 @@ func NewServer(options Options) (*Server, error) { // list of past cycles mux.HandleFunc("/history", server.handlerHistory) - mux.HandleFunc("/user", server.handlerUser) - mux.HandleFunc("/user/login", server.handlerUserLogin) - mux.HandleFunc("/oauth/twitch", server.handlerTwitchOAuth) mux.HandleFunc("/oauth/twitch/callback", server.handlerTwitchOAuthCallback) @@ -216,6 +213,8 @@ func NewServer(options Options) (*Server, error) { mux.HandleFunc("/oauth/patreon", server.handlerPatreonOAuth) mux.HandleFunc("/oauth/patreon/callback", server.handlerPatreonOAuthCallback) + mux.HandleFunc("/user", server.handlerUser) + mux.HandleFunc("/user/login", server.handlerUserLogin) mux.HandleFunc("/user/logout", server.handlerUserLogout) mux.HandleFunc("/user/new", server.handlerUserNew) mux.HandleFunc("/user/remove/local", server.handlerLocalAuthRemove) From dc3c85c25e8d1eb35015d789b2d33451f7228aca Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 02:03:08 +0100 Subject: [PATCH 51/67] Removed the field RateLimitOverwrite field from the user This closes #79. --- admin.go | 1 - common/user.go | 4 +--- data/json.go | 5 +---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/admin.go b/admin.go index 0b2a888..661719e 100644 --- a/admin.go +++ b/admin.go @@ -103,7 +103,6 @@ func (s *Server) adminDeleteUser(w http.ResponseWriter, r *http.Request, user *c user.NotifyCycleEnd = false user.NotifyVoteSelection = false user.Privilege = 0 - user.RateLimitOverride = false err := s.data.UpdateUser(user) if err != nil { diff --git a/common/user.go b/common/user.go index 9fd4605..528a850 100644 --- a/common/user.go +++ b/common/user.go @@ -24,9 +24,7 @@ type User struct { AuthMethods []*AuthMethod - // Does this user ignore rate limit? (default true for mod/admin) - RateLimitOverride bool - LastMovieAdd time.Time + LastMovieAdd time.Time } func (u User) CheckPriv(lvl string) bool { diff --git a/data/json.go b/data/json.go index f4ae1c2..f9fcd75 100644 --- a/data/json.go +++ b/data/json.go @@ -110,8 +110,7 @@ type jsonUser struct { AuthMethods []int - RateLimitOverride bool - LastMovieAdd *time.Time + LastMovieAdd *time.Time } func (j *jsonConnector) newJsonUser(user *common.User) jsonUser { @@ -132,7 +131,6 @@ func (j *jsonConnector) newJsonUser(user *common.User) jsonUser { NotifyVoteSelection: user.NotifyVoteSelection, Privilege: int(user.Privilege), AuthMethods: authMethods, - RateLimitOverride: user.RateLimitOverride, LastMovieAdd: &user.LastMovieAdd, } @@ -609,7 +607,6 @@ func (j *jsonConnector) userFromJson(jUser jsonUser) *common.User { NotifyCycleEnd: jUser.NotifyCycleEnd, NotifyVoteSelection: jUser.NotifyVoteSelection, AuthMethods: authMethods, - RateLimitOverride: jUser.RateLimitOverride, LastMovieAdd: *jUser.LastMovieAdd, Privilege: common.PrivilegeLevel(jUser.Privilege), } From 3d19f027c279a8182df366b9819079b8d28a0f4e Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 03:54:22 +0100 Subject: [PATCH 52/67] Added a forgotten "ensurance" of the AuthMethods map --- data/json.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/json.go b/data/json.go index f9fcd75..87fb06b 100644 --- a/data/json.go +++ b/data/json.go @@ -273,6 +273,10 @@ func loadJson(filename string, l *common.Logger) (*jsonConnector, error) { data.Links = make(map[int]*common.Link) } + if data.AuthMethods == nil { + data.AuthMethods = make(map[int]*common.AuthMethod) + } + return data, nil } From 87d6d146440b5048fecb84db5643c3d775c2648a Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 04:00:48 +0100 Subject: [PATCH 53/67] Removed the "LastMovieAdd" field from the user. While i was at it i also removed it in the (outdated) tests. --- common/user.go | 2 -- data/connectors_test.go | 1 - data/helpers_test.go | 3 --- data/json.go | 4 ---- 4 files changed, 10 deletions(-) diff --git a/common/user.go b/common/user.go index 528a850..0245d2e 100644 --- a/common/user.go +++ b/common/user.go @@ -23,8 +23,6 @@ type User struct { Privilege PrivilegeLevel AuthMethods []*AuthMethod - - LastMovieAdd time.Time } func (u User) CheckPriv(lvl string) bool { diff --git a/data/connectors_test.go b/data/connectors_test.go index 74ba9b8..f7999b0 100644 --- a/data/connectors_test.go +++ b/data/connectors_test.go @@ -171,7 +171,6 @@ func Test_AddUser(t *testing.T) { Privilege: common.PRIV_MOD, PassDate: passDate, RateLimitOverride: true, - LastMovieAdd: passDate.Add(time.Hour), } uid, err := conn.AddUser(testUser) diff --git a/data/helpers_test.go b/data/helpers_test.go index ced5405..8d704e7 100644 --- a/data/helpers_test.go +++ b/data/helpers_test.go @@ -130,9 +130,6 @@ func compareUsers(a, b *common.User, t *testing.T) { // t.Fatalf("RateLimitOverride mismatch: %t vs %t", user.RateLimitOverride, loggedIn.RateLimitOverride) //} - //if user.LastMovieAdd != loggedIn.LastMovieAdd { - // t.Fatalf("LastMovieAdd mismatch: %s vs %s", user.LastMovieAdd, loggedIn.LastMovieAdd) - //} } func compareMovies(a, b *common.Movie, t *testing.T) { diff --git a/data/json.go b/data/json.go index 87fb06b..2e3782e 100644 --- a/data/json.go +++ b/data/json.go @@ -109,8 +109,6 @@ type jsonUser struct { Privilege int AuthMethods []int - - LastMovieAdd *time.Time } func (j *jsonConnector) newJsonUser(user *common.User) jsonUser { @@ -131,7 +129,6 @@ func (j *jsonConnector) newJsonUser(user *common.User) jsonUser { NotifyVoteSelection: user.NotifyVoteSelection, Privilege: int(user.Privilege), AuthMethods: authMethods, - LastMovieAdd: &user.LastMovieAdd, } return ju @@ -611,7 +608,6 @@ func (j *jsonConnector) userFromJson(jUser jsonUser) *common.User { NotifyCycleEnd: jUser.NotifyCycleEnd, NotifyVoteSelection: jUser.NotifyVoteSelection, AuthMethods: authMethods, - LastMovieAdd: *jUser.LastMovieAdd, Privilege: common.PrivilegeLevel(jUser.Privilege), } From 2a169310dd04599c52998dfd114987826d1d5a4a Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 04:03:37 +0100 Subject: [PATCH 54/67] Added new migration script "migrateUsers.go" --- cmd/migrateUsers.go | 126 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 cmd/migrateUsers.go diff --git a/cmd/migrateUsers.go b/cmd/migrateUsers.go new file mode 100644 index 0000000..c825cd5 --- /dev/null +++ b/cmd/migrateUsers.go @@ -0,0 +1,126 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/mitchellh/mapstructure" + mpc "github.com/zorchenhimer/MoviePolls/common" + mpd "github.com/zorchenhimer/MoviePolls/data" +) + +const jsonFilename string = "db/data.json" + +func main() { + err := loadOldDB() + if err != nil { + fmt.Printf("%v\n", err) + } +} + +type OldUser struct { + Id int + Name string + Password string + OAuthToken string + Email string + NotifyCycleEnd bool + NotifyVoteSelection bool + Priviledge int + PassDate time.Time + RateLimitOverride bool + LastMovieAdd time.Time +} + +func loadOldDB() error { + + data, err := ioutil.ReadFile(jsonFilename) + if err != nil { + fmt.Println(err) + } + + var fullData map[string]interface{} + + json.Unmarshal(data, &fullData) + + // fullData now contains the whole db + jUsers := fullData["Users"].(map[string]interface{}) + oldUsers := []OldUser{} + for _, user := range jUsers { + ou := &OldUser{} + mapstructure.Decode(user, &ou) + ou.PassDate, _ = time.Parse(time.RFC3339, user.(map[string]interface{})["PassDate"].(string)) + oldUsers = append(oldUsers, *ou) + } + + delete(fullData, "Users") + + data, err = json.MarshalIndent(fullData, "", " ") + + // write a temporary file which contains the db without the "users" + err = ioutil.WriteFile("db/temp.json", data, 0666) + if err != nil { + return err + } + + l, err := mpc.NewLogger(mpc.LLDebug, "") + + if err != nil { + return err + } + + dc, err := mpd.GetDataConnector("json", "db/temp.json", l) + + if err != nil { + return err + } + + for _, oldUser := range oldUsers { + + l.Debug("Old User Passdate: %s", oldUser.PassDate) + + // convert old movies to new ones + newUser := mpc.User{ + Id: oldUser.Id, + Name: oldUser.Name, + Email: oldUser.Email, + NotifyCycleEnd: oldUser.NotifyCycleEnd, + NotifyVoteSelection: oldUser.NotifyVoteSelection, + Privilege: mpc.PrivilegeLevel(oldUser.Priviledge), + } + + // Do NOT build new entries for users with empty passwords (i.e. Deleted users) + if oldUser.Password != "" { + authMethod := &mpc.AuthMethod{ + Type: mpc.AUTH_LOCAL, + Password: oldUser.Password, + PassDate: oldUser.PassDate, + } + + l.Debug("New User Passdate: %v", authMethod.PassDate) + + newUser.AuthMethods = []*mpc.AuthMethod{} + + id, err := dc.AddAuthMethod(authMethod) + + if err != nil { + return err + } + + authMethod.Id = id + + newUser.AuthMethods = append(newUser.AuthMethods, authMethod) + } + dc.UpdateUser(&newUser) + } + + err = os.Rename("db/temp.json", jsonFilename) + if err != nil { + return err + } + + return nil +} From 2965e7ef7ae03cbc80ceea0ef396ab686813e550 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 04:05:39 +0100 Subject: [PATCH 55/67] Remove unused import --- common/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/common/user.go b/common/user.go index 0245d2e..a3773b7 100644 --- a/common/user.go +++ b/common/user.go @@ -2,7 +2,6 @@ package common import ( "fmt" - "time" ) type PrivilegeLevel int From 67274830d0fbcdf689a12ff9ca4ea900b6fd099a Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 04:48:08 +0100 Subject: [PATCH 56/67] Fixed a stacktrace when logging in with a non existin username --- data/json.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/data/json.go b/data/json.go index 2e3782e..d46af60 100644 --- a/data/json.go +++ b/data/json.go @@ -645,16 +645,18 @@ func (j *jsonConnector) UserLocalLogin(name, hashedPw string) (*common.User, err user := j.findUserByName(name) - for _, auth := range user.AuthMethods { - if auth.Type == "Local" { - if hashedPw == auth.Password { - return user, nil + if user != nil { + for _, auth := range user.AuthMethods { + if auth.Type == "Local" { + if hashedPw == auth.Password { + return user, nil + } + j.l.Info("Bad password for user %s\n", name) + return nil, fmt.Errorf("Invalid login credentials") } - j.l.Info("Bad password for user %s\n", name) - return nil, fmt.Errorf("Invalid login credentials") } - } + } j.l.Info("User with name %s not found\n", name) return nil, fmt.Errorf("Invalid login credentials") } From 5f0a28b33f805c57f50ad086958eb66b284735c7 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 16 Jan 2021 04:48:58 +0100 Subject: [PATCH 57/67] Fixed a typo and parse the time.Time by hand for REASONS --- cmd/migrateUsers.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cmd/migrateUsers.go b/cmd/migrateUsers.go index c825cd5..7282226 100644 --- a/cmd/migrateUsers.go +++ b/cmd/migrateUsers.go @@ -29,7 +29,7 @@ type OldUser struct { Email string NotifyCycleEnd bool NotifyVoteSelection bool - Priviledge int + Privilege int PassDate time.Time RateLimitOverride bool LastMovieAdd time.Time @@ -52,7 +52,16 @@ func loadOldDB() error { for _, user := range jUsers { ou := &OldUser{} mapstructure.Decode(user, &ou) - ou.PassDate, _ = time.Parse(time.RFC3339, user.(map[string]interface{})["PassDate"].(string)) + // lets parse the fucking time by hand + if user.(map[string]interface{})["PassDate"] == nil { + fmt.Println("[ERROR] Could not parse field 'PassDate', was the database already converted?") + return nil + } + ou.PassDate, err = time.Parse(time.RFC3339, user.(map[string]interface{})["PassDate"].(string)) + if err != nil { + fmt.Println("[ERROR] Could not parse field 'PassDate', was the database already converted?") + return nil + } oldUsers = append(oldUsers, *ou) } @@ -80,8 +89,6 @@ func loadOldDB() error { for _, oldUser := range oldUsers { - l.Debug("Old User Passdate: %s", oldUser.PassDate) - // convert old movies to new ones newUser := mpc.User{ Id: oldUser.Id, @@ -89,7 +96,16 @@ func loadOldDB() error { Email: oldUser.Email, NotifyCycleEnd: oldUser.NotifyCycleEnd, NotifyVoteSelection: oldUser.NotifyVoteSelection, - Privilege: mpc.PrivilegeLevel(oldUser.Priviledge), + Privilege: mpc.PrivilegeLevel(oldUser.Privilege), + } + + switch oldUser.Privilege { + case 0: + newUser.Privilege = mpc.PRIV_USER + case 1: + newUser.Privilege = mpc.PRIV_MOD + case 2: + newUser.Privilege = mpc.PRIV_ADMIN } // Do NOT build new entries for users with empty passwords (i.e. Deleted users) @@ -100,8 +116,6 @@ func loadOldDB() error { PassDate: oldUser.PassDate, } - l.Debug("New User Passdate: %v", authMethod.PassDate) - newUser.AuthMethods = []*mpc.AuthMethod{} id, err := dc.AddAuthMethod(authMethod) From a06a6209206e90d4f47b3cecb32d63ee5a4d55c6 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 18 Jan 2021 02:23:52 +0100 Subject: [PATCH 58/67] Minor changes found in my review --- cmd/migrateUsers.go | 9 --------- oauth.go | 3 +-- user.go | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/cmd/migrateUsers.go b/cmd/migrateUsers.go index 7282226..42f67b3 100644 --- a/cmd/migrateUsers.go +++ b/cmd/migrateUsers.go @@ -99,15 +99,6 @@ func loadOldDB() error { Privilege: mpc.PrivilegeLevel(oldUser.Privilege), } - switch oldUser.Privilege { - case 0: - newUser.Privilege = mpc.PRIV_USER - case 1: - newUser.Privilege = mpc.PRIV_MOD - case 2: - newUser.Privilege = mpc.PRIV_ADMIN - } - // Do NOT build new entries for users with empty passwords (i.e. Deleted users) if oldUser.Password != "" { authMethod := &mpc.AuthMethod{ diff --git a/oauth.go b/oauth.go index 2dbdb2a..2eac4e6 100644 --- a/oauth.go +++ b/oauth.go @@ -18,7 +18,6 @@ var twitchOAuthConfig = &oauth2.Config{} var discordOAuthConfig = &oauth2.Config{} var patreonOAuthConfig = &oauth2.Config{} -// var oauthStateString string var openStates = []string{} // Welp we need to do the endpoints ourself i guess ... @@ -405,7 +404,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque NotifyVoteSelection: false, } - // add this new server to the database + // add this new user to the database newUser.Id, err = s.data.AddUser(newUser) if err != nil { diff --git a/user.go b/user.go index 5829dae..2c58580 100644 --- a/user.go +++ b/user.go @@ -224,7 +224,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { } if !(data.ErrCurrentPass || data.ErrNewPass || data.ErrEmail) { // Change pass - data.SuccessMessage = "Password successfully changed" + data.SuccessMessage = "Password successfully set" localAuth.Password = s.hashPassword(pass1_raw) localAuth.PassDate = time.Now() s.l.Info("new PassDate: %s", localAuth.PassDate) From 3f5bbf690035e3294ef51d326a1e8ce5b233e154 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 18 Jan 2021 02:56:01 +0100 Subject: [PATCH 59/67] Added new config values to en/disable oauth signup --- admin.go | 3 +++ auth.go | 1 - server.go | 42 ++++++++++++++++++++++----------------- templates/newaccount.html | 6 +++--- user.go | 35 ++++++++++++++++++++++++++++---- 5 files changed, 61 insertions(+), 26 deletions(-) diff --git a/admin.go b/admin.go index 661719e..f5d5231 100644 --- a/admin.go +++ b/admin.go @@ -390,12 +390,15 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { configValue{Key: ConfigUnlimitedVotes, Default: DefaultUnlimitedVotes, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthEnabled, Default: DefaultTwitchOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigTwitchOauthSignupEnabled, Default: DefaultTwitchOauthSignupEnabled, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthClientID, Default: DefaultTwitchOauthClientID, Type: ConfigString}, configValue{Key: ConfigTwitchOauthClientSecret, Default: DefaultTwitchOauthClientSecret, Type: ConfigString}, configValue{Key: ConfigDiscordOauthEnabled, Default: DefaultDiscordOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigDiscordOauthSignupEnabled, Default: DefaultDiscordOauthSignupEnabled, Type: ConfigBool}, configValue{Key: ConfigDiscordOauthClientID, Default: DefaultDiscordOauthClientID, Type: ConfigString}, configValue{Key: ConfigDiscordOauthClientSecret, Default: DefaultDiscordOauthClientSecret, Type: ConfigString}, configValue{Key: ConfigPatreonOauthEnabled, Default: DefaultPatreonOauthEnabled, Type: ConfigBool}, + configValue{Key: ConfigPatreonOauthSignupEnabled, Default: DefaultPatreonOauthSignupEnabled, Type: ConfigBool}, configValue{Key: ConfigPatreonOauthClientID, Default: DefaultPatreonOauthClientID, Type: ConfigString}, configValue{Key: ConfigPatreonOauthClientSecret, Default: DefaultPatreonOauthClientSecret, Type: ConfigString}, }, diff --git a/auth.go b/auth.go index e805e6c..e22a41a 100644 --- a/auth.go +++ b/auth.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "regexp" - //"strconv" "strings" "time" diff --git a/server.go b/server.go index 72422e9..a7618dc 100644 --- a/server.go +++ b/server.go @@ -38,15 +38,18 @@ const ( DefaultMaxLinkLength int = 500 // length of all links combined DefaultMaxRemarksLength int = 200 - DefaultTwitchOauthEnabled bool = false - DefaultTwitchOauthClientID string = "" - DefaultTwitchOauthClientSecret string = "" - DefaultDiscordOauthEnabled bool = false - DefaultDiscordOauthClientID string = "" - DefaultDiscordOauthClientSecret string = "" - DefaultPatreonOauthEnabled bool = false - DefaultPatreonOauthClientID string = "" - DefaultPatreonOauthClientSecret string = "" + DefaultTwitchOauthEnabled bool = false + DefaultTwitchOauthSignupEnabled bool = false + DefaultTwitchOauthClientID string = "" + DefaultTwitchOauthClientSecret string = "" + DefaultDiscordOauthEnabled bool = false + DefaultDiscordOauthSignupEnabled bool = false + DefaultDiscordOauthClientID string = "" + DefaultDiscordOauthClientSecret string = "" + DefaultPatreonOauthEnabled bool = false + DefaultPatreonOauthSignupEnabled bool = false + DefaultPatreonOauthClientID string = "" + DefaultPatreonOauthClientSecret string = "" ) // configuration keys @@ -71,15 +74,18 @@ const ( ConfigMaxLinkLength string = "MaxLinkLength" ConfigMaxRemarksLength string = "MaxRemarksLength" - ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" - ConfigTwitchOauthClientID string = "TwitchOauthClientID" - ConfigTwitchOauthClientSecret string = "TwitchOauthSecret" - ConfigDiscordOauthEnabled string = "DiscordOauthEnabled" - ConfigDiscordOauthClientID string = "DiscordOauthClientID" - ConfigDiscordOauthClientSecret string = "DiscordOauthClientSecret" - ConfigPatreonOauthEnabled string = "PatreonOauthEnabled" - ConfigPatreonOauthClientID string = "PatreonOauthClientID" - ConfigPatreonOauthClientSecret string = "PatreonOauthClientSecret" + ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" + ConfigTwitchOauthSignupEnabled string = "TwitchOauthSignupEnabled" + ConfigTwitchOauthClientID string = "TwitchOauthClientID" + ConfigTwitchOauthClientSecret string = "TwitchOauthSecret" + ConfigDiscordOauthEnabled string = "DiscordOauthEnabled" + ConfigDiscordOauthSignupEnabled string = "DiscordOauthSignupEnabled" + ConfigDiscordOauthClientID string = "DiscordOauthClientID" + ConfigDiscordOauthClientSecret string = "DiscordOauthClientSecret" + ConfigPatreonOauthEnabled string = "PatreonOauthEnabled" + ConfigPatreonOauthSignupEnabled string = "PatreonOauthSignupEnabled" + ConfigPatreonOauthClientID string = "PatreonOauthClientID" + ConfigPatreonOauthClientSecret string = "PatreonOauthClientSecret" ) type Options struct { diff --git a/templates/newaccount.html b/templates/newaccount.html index 595407d..edb6cd0 100644 --- a/templates/newaccount.html +++ b/templates/newaccount.html @@ -24,13 +24,13 @@
    {{if .OAuth}}
    - {{if .TwitchOAuth}} + {{if and .TwitchOAuth .TwitchSignup}} Signup with Twitch {{end}} - {{if .DiscordOAuth}} + {{if and .DiscordOAuth .DiscordSignup}} Signup with Discord {{end}} - {{if .PatreonOAuth}} + {{if and .PatreonOAuth .PatreonSignup}} Signup with Patreon {{end}}
    diff --git a/user.go b/user.go index 2c58580..2c55760 100644 --- a/user.go +++ b/user.go @@ -413,10 +413,13 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { ErrPass bool ErrEmail bool - OAuth bool - TwitchOAuth bool - DiscordOAuth bool - PatreonOAuth bool + OAuth bool + TwitchOAuth bool + TwitchSignup bool + DiscordOAuth bool + DiscordSignup bool + PatreonOAuth bool + PatreonSignup bool ValName string ValEmail string @@ -436,6 +439,14 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { } data.TwitchOAuth = twitchAuth + twitchSignup, err := s.data.GetCfgBool(ConfigTwitchOauthSignupEnabled, DefaultTwitchOauthSignupEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) + return + } + data.TwitchSignup = twitchSignup + discordAuth, err := s.data.GetCfgBool(ConfigDiscordOauthEnabled, DefaultDiscordOauthEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) @@ -444,6 +455,14 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { } data.DiscordOAuth = discordAuth + discordSignup, err := s.data.GetCfgBool(ConfigDiscordOauthSignupEnabled, DefaultDiscordOauthSignupEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) + return + } + data.DiscordSignup = discordSignup + patreonAuth, err := s.data.GetCfgBool(ConfigPatreonOauthEnabled, DefaultPatreonOauthEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) @@ -452,6 +471,14 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { } data.PatreonOAuth = patreonAuth + patreonSignup, err := s.data.GetCfgBool(ConfigPatreonOauthSignupEnabled, DefaultPatreonOauthSignupEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) + return + } + data.PatreonSignup = patreonSignup + data.OAuth = twitchAuth || discordAuth || patreonAuth if r.Method == "POST" { From 04f9a6ecdd2792262a895b1986d89b2f44f4009d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 18 Jan 2021 03:42:56 +0100 Subject: [PATCH 60/67] Added an other config value to en/disable local signup Changed the signup page accordingly --- admin.go | 42 +++++++++++++++++++++++++++++++++++++++ server.go | 2 ++ templates/newaccount.html | 2 ++ user.go | 15 +++++++++++--- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/admin.go b/admin.go index f5d5231..680e673 100644 --- a/admin.go +++ b/admin.go @@ -389,6 +389,7 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { configValue{Key: ConfigUnlimitedVotes, Default: DefaultUnlimitedVotes, Type: ConfigBool}, + configValue{Key: ConfigLocalSignupEnabled, Default: DefaultLocalSignupEnabled, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthEnabled, Default: DefaultTwitchOauthEnabled, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthSignupEnabled, Default: DefaultTwitchOauthSignupEnabled, Type: ConfigBool}, configValue{Key: ConfigTwitchOauthClientID, Default: DefaultTwitchOauthClientID, Type: ConfigString}, @@ -512,6 +513,47 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { data.ErrorMessage = append(data.ErrorMessage, err.Error()) } + // getting ALL the booleans + var localSignup, twitchSignup, patreonSignup, discordSignup, twitchOauth, patreonOauth, discordOauth bool + + // lets better hope that THIS NEVER FAILS to assign a valid boolean + for _, val := range data.Values { + switch val.Key { + case ConfigLocalSignupEnabled: + localSignup = val.Value.(bool) + case ConfigTwitchOauthSignupEnabled: + twitchSignup = val.Value.(bool) + case ConfigTwitchOauthEnabled: + twitchOauth = val.Value.(bool) + case ConfigDiscordOauthSignupEnabled: + discordSignup = val.Value.(bool) + case ConfigDiscordOauthEnabled: + discordOauth = val.Value.(bool) + case ConfigPatreonOauthSignupEnabled: + patreonSignup = val.Value.(bool) + case ConfigPatreonOauthEnabled: + patreonOauth = val.Value.(bool) + } + } + + // Check that we have atleast ONE signup method enabled + if !(localSignup || twitchSignup || discordSignup || patreonSignup) { + data.ErrorMessage = append(data.ErrorMessage, "No Signup method is currently enabled, please ensure to enable atleast one method") + } + + // Check that the corresponding oauth for the signup is enabled + if twitchSignup && !twitchOauth { + data.ErrorMessage = append(data.ErrorMessage, "To enable twitch signup you need to also enable twitch Oauth (and fill the token/secret)") + } + + if discordSignup && !discordOauth { + data.ErrorMessage = append(data.ErrorMessage, "To enable discord signup you need to also enable discord Oauth (and fill the token/secret)") + } + + if patreonSignup && !patreonOauth { + data.ErrorMessage = append(data.ErrorMessage, "To enable patreon signup you need to also enable patreon Oauth (and fill the token/secret)") + } + if err := s.executeTemplate(w, "adminConfig", data); err != nil { s.l.Error("Error rendering template: %v", err) } diff --git a/server.go b/server.go index a7618dc..3ae4872 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,7 @@ const ( DefaultMaxLinkLength int = 500 // length of all links combined DefaultMaxRemarksLength int = 200 + DefaultLocalSignupEnabled bool = true DefaultTwitchOauthEnabled bool = false DefaultTwitchOauthSignupEnabled bool = false DefaultTwitchOauthClientID string = "" @@ -74,6 +75,7 @@ const ( ConfigMaxLinkLength string = "MaxLinkLength" ConfigMaxRemarksLength string = "MaxRemarksLength" + ConfigLocalSignupEnabled string = "LocalSignupEnabled" ConfigTwitchOauthEnabled string = "TwitchOauthEnabled" ConfigTwitchOauthSignupEnabled string = "TwitchOauthSignupEnabled" ConfigTwitchOauthClientID string = "TwitchOauthClientID" diff --git a/templates/newaccount.html b/templates/newaccount.html index edb6cd0..65d28ed 100644 --- a/templates/newaccount.html +++ b/templates/newaccount.html @@ -10,6 +10,7 @@
    {{end}}
    + {{ if .LocalSignup }}
    @@ -22,6 +23,7 @@
    + {{end}} {{if .OAuth}}
    {{if and .TwitchOAuth .TwitchSignup}} diff --git a/user.go b/user.go index 2c55760..5e351c1 100644 --- a/user.go +++ b/user.go @@ -420,6 +420,7 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { DiscordSignup bool PatreonOAuth bool PatreonSignup bool + LocalSignup bool ValName string ValEmail string @@ -442,7 +443,7 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { twitchSignup, err := s.data.GetCfgBool(ConfigTwitchOauthSignupEnabled, DefaultTwitchOauthSignupEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) - s.l.Error("Unable to get ConfigTwitchOauthEnabled config value: %v", err) + s.l.Error("Unable to get ConfigTwitchOauthSignupEnabled config value: %v", err) return } data.TwitchSignup = twitchSignup @@ -458,7 +459,7 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { discordSignup, err := s.data.GetCfgBool(ConfigDiscordOauthSignupEnabled, DefaultDiscordOauthSignupEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) - s.l.Error("Unable to get ConfigDiscordOauthEnabled config value: %v", err) + s.l.Error("Unable to get ConfigDiscordOauthSignupEnabled config value: %v", err) return } data.DiscordSignup = discordSignup @@ -474,11 +475,19 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { patreonSignup, err := s.data.GetCfgBool(ConfigPatreonOauthSignupEnabled, DefaultPatreonOauthSignupEnabled) if err != nil { s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) - s.l.Error("Unable to get ConfigPatreonOauthEnabled config value: %v", err) + s.l.Error("Unable to get ConfigPatreonOauthSignupEnabled config value: %v", err) return } data.PatreonSignup = patreonSignup + localSignup, err := s.data.GetCfgBool(ConfigLocalSignupEnabled, DefaultLocalSignupEnabled) + if err != nil { + s.doError(http.StatusInternalServerError, "Something went wrong :C", w, r) + s.l.Error("Unable to get ConfigLocalSignupEnabled config value: %v", err) + return + } + data.LocalSignup = localSignup + data.OAuth = twitchAuth || discordAuth || patreonAuth if r.Method == "POST" { From 256b5423b518fa0ed1df5c46ffb2645fa7ff4539 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 18 Jan 2021 04:20:15 +0100 Subject: [PATCH 61/67] Added a check to prevent users having no valid login method Added a new function called GetUsersWithAuth(authType, boolean) that returns all users having the provided authType associated to them. The boolean triggers an exclusively check to enable searching for users having ONLY this authType. --- admin.go | 16 ++++++++++++++++ data/connector.go | 1 + data/json.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/admin.go b/admin.go index 680e673..0a7e4ff 100644 --- a/admin.go +++ b/admin.go @@ -554,6 +554,22 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { data.ErrorMessage = append(data.ErrorMessage, "To enable patreon signup you need to also enable patreon Oauth (and fill the token/secret)") } + // i intentionally ignore the errors here since they also idicate that no user was found + users, _ := s.data.GetUsersWithAuth(common.AUTH_TWITCH, true) + if (len(users) != 0) && !twitchOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Twitch Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } + + users, _ = s.data.GetUsersWithAuth(common.AUTH_PATREON, true) + if (len(users) != 0) && !patreonOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Patreon Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } + + users, _ = s.data.GetUsersWithAuth(common.AUTH_DISCORD, true) + if (len(users) != 0) && !discordOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Discord Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } + if err := s.executeTemplate(w, "adminConfig", data); err != nil { s.l.Error("Error rendering template: %v", err) } diff --git a/data/connector.go b/data/connector.go index d0c3c0c..7e99f9d 100644 --- a/data/connector.go +++ b/data/connector.go @@ -56,6 +56,7 @@ type DataConnector interface { GetUsers(start, count int) ([]*common.User, error) GetUserVotes(userId int) ([]*common.Movie, error) GetUserMovies(userId int) ([]*common.Movie, error) + GetUsersWithAuth(auth common.AuthType, exclusive bool) ([]*common.User, error) //GetMovieVotes(userId int) []*Movie GetTag(id int) *common.Tag GetAuthMethod(id int) *common.AuthMethod diff --git a/data/json.go b/data/json.go index d46af60..6a94d05 100644 --- a/data/json.go +++ b/data/json.go @@ -1579,3 +1579,32 @@ func (j *jsonConnector) CheckOauthUsage(id string, authType common.AuthType) boo } return false } + +func (j *jsonConnector) GetUsersWithAuth(auth common.AuthType, exclusive bool) ([]*common.User, error) { + j.lock.Lock() + defer j.lock.Unlock() + + res := []*common.User{} + + for _, juser := range j.Users { + user := j.userFromJson(juser) + for _, authMethod := range user.AuthMethods { + if authMethod.Type == auth { + // user has atleast this auth method + if exclusive { + if len(user.AuthMethods) == 1 { + // user has ONLY this auth method + res = append(res, user) + } + } else { + res = append(res, user) + } + + } + } + } + if len(res) == 0 { + return nil, fmt.Errorf("No users with authmethod %s found.", auth) + } + return res, nil +} From 58c3174e953125fd97585db0451ce200823b3355 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Mon, 25 Jan 2021 02:15:31 +0100 Subject: [PATCH 62/67] Added a new function in link.go This function is used to clean up mobile modifiers in (for now) imdb links. I created the function to later adapt it for more urls (now it just uses strings.Replace). This fixes #91 --- common/link.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/link.go b/common/link.go index 04d9782..9d04bf6 100644 --- a/common/link.go +++ b/common/link.go @@ -49,6 +49,7 @@ func (l *Link) validateLink() error { url := l.Url if re_validLink.MatchString(url) { url = stripRefFromLink(url) + url = stripMobileFromLink(url) if len(url) <= 8 { // lets be stupid when the link is too short @@ -76,6 +77,12 @@ func stripRefFromLink(link string) string { return link } +// for now we just replace it for imdb, if we need others we need to adapt that +// is it stupid, YES, does it work, i guess? do i have a better idea, no +func stripMobileFromLink(link string) string { + return strings.Replace(link, "m.imdb.com", "imdb.com", 1) +} + func (l *Link) determineLinkType() error { if strings.Contains(l.Url, "imdb") { l.Type = "IMDb" From 40c713050d836795b8adf8bc8bb21ae95154f6c1 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 30 Jan 2021 22:04:44 +0100 Subject: [PATCH 63/67] Improved boolean casting for config values --- admin.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/admin.go b/admin.go index 0a7e4ff..87dffd6 100644 --- a/admin.go +++ b/admin.go @@ -516,23 +516,27 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { // getting ALL the booleans var localSignup, twitchSignup, patreonSignup, discordSignup, twitchOauth, patreonOauth, discordOauth bool - // lets better hope that THIS NEVER FAILS to assign a valid boolean for _, val := range data.Values { + bval, ok := val.Value.(bool) + if !ok { + data.ErrorMessage = append(data.ErrorMessage, "Could not parse field %s as boolean", val.Key) + break + } switch val.Key { case ConfigLocalSignupEnabled: - localSignup = val.Value.(bool) + localSignup = bval case ConfigTwitchOauthSignupEnabled: - twitchSignup = val.Value.(bool) + twitchSignup = bval case ConfigTwitchOauthEnabled: - twitchOauth = val.Value.(bool) + twitchOauth = bval case ConfigDiscordOauthSignupEnabled: - discordSignup = val.Value.(bool) + discordSignup = bval case ConfigDiscordOauthEnabled: - discordOauth = val.Value.(bool) + discordOauth = bval case ConfigPatreonOauthSignupEnabled: - patreonSignup = val.Value.(bool) + patreonSignup = bval case ConfigPatreonOauthEnabled: - patreonOauth = val.Value.(bool) + patreonOauth = bval } } From 3ec9c3a43b8ce7547ed481e2b2a9d63879a67e53 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 30 Jan 2021 22:28:13 +0100 Subject: [PATCH 64/67] Bugfix for previous commit --- admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin.go b/admin.go index 87dffd6..a0d850e 100644 --- a/admin.go +++ b/admin.go @@ -518,7 +518,7 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { for _, val := range data.Values { bval, ok := val.Value.(bool) - if !ok { + if !ok && val.Type == ConfigBool { data.ErrorMessage = append(data.ErrorMessage, "Could not parse field %s as boolean", val.Key) break } From 9440f310537d68d2df5ee3daf6a30a7fc0bde53d Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 30 Jan 2021 22:30:14 +0100 Subject: [PATCH 65/67] Unified date fields in auth struct to Date vs Pass/RefreshDate This simplifies the session management as mentioned by @zorchenhimer in the discussion of #93 --- auth.go | 2 +- cmd/migrateUsers.go | 2 +- common/authmethod.go | 3 +- oauth.go | 12 ++++---- session.go | 69 ++++++++++++++------------------------------ user.go | 10 +++---- 6 files changed, 35 insertions(+), 63 deletions(-) diff --git a/auth.go b/auth.go index e22a41a..2c09d50 100644 --- a/auth.go +++ b/auth.go @@ -128,7 +128,7 @@ func (s *Server) handlerAuth(w http.ResponseWriter, r *http.Request) { } localAuth.Password = s.hashPassword(pass1) - localAuth.PassDate = time.Now() + localAuth.Date = time.Now() if err = s.data.UpdateAuthMethod(localAuth); err != nil { s.l.Error("Unable to save AuthMethod with new password:", err) diff --git a/cmd/migrateUsers.go b/cmd/migrateUsers.go index 42f67b3..349fca2 100644 --- a/cmd/migrateUsers.go +++ b/cmd/migrateUsers.go @@ -104,7 +104,7 @@ func loadOldDB() error { authMethod := &mpc.AuthMethod{ Type: mpc.AUTH_LOCAL, Password: oldUser.Password, - PassDate: oldUser.PassDate, + Date: oldUser.PassDate, } newUser.AuthMethods = []*mpc.AuthMethod{} diff --git a/common/authmethod.go b/common/authmethod.go index 3b215e1..a2d943c 100644 --- a/common/authmethod.go +++ b/common/authmethod.go @@ -16,8 +16,7 @@ type AuthMethod struct { ExtId string Type AuthType Password string - PassDate time.Time AuthToken string RefreshToken string - RefreshDate time.Time + Date time.Time } diff --git a/oauth.go b/oauth.go index 2eac4e6..e7b3a77 100644 --- a/oauth.go +++ b/oauth.go @@ -391,7 +391,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque ExtId: data["data"][0]["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } // check if Twitch Auth is already used @@ -460,7 +460,7 @@ func (s *Server) handlerTwitchOAuthCallback(w http.ResponseWriter, r *http.Reque ExtId: data["data"][0]["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } // check if this oauth is already used @@ -672,7 +672,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ ExtId: data["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { @@ -732,7 +732,7 @@ func (s *Server) handlerDiscordOAuthCallback(w http.ResponseWriter, r *http.Requ ExtId: data["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { @@ -940,7 +940,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ ExtId: data["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { @@ -1001,7 +1001,7 @@ func (s *Server) handlerPatreonOAuthCallback(w http.ResponseWriter, r *http.Requ ExtId: data["id"].(string), AuthToken: token.AccessToken, RefreshToken: token.RefreshToken, - RefreshDate: token.Expiry, + Date: token.Expiry, } if !s.data.CheckOauthUsage(auth.ExtId, auth.Type) { diff --git a/session.go b/session.go index 13f873d..00ad747 100644 --- a/session.go +++ b/session.go @@ -31,49 +31,22 @@ func (s *Server) login(user *common.User, authType common.AuthType, w http.Respo session.Values["UserId"] = user.Id - switch authType { - case common.AUTH_LOCAL: - - gobbed, err := auth.PassDate.GobEncode() - if err != nil { - return fmt.Errorf("Unable to gob PassDate") - } - - session.Values["PassDate"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) - case common.AUTH_TWITCH: - gobbed, err := auth.RefreshDate.GobEncode() - if err != nil { - return fmt.Errorf("Unable to gob RefreshDate") - } - - session.Values["RefreshDate_Twitch"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) - case common.AUTH_PATREON: - gobbed, err := auth.RefreshDate.GobEncode() - if err != nil { - return fmt.Errorf("Unable to gob RefreshDate") - } - - session.Values["RefreshDate_Patreon"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) - case common.AUTH_DISCORD: - gobbed, err := auth.RefreshDate.GobEncode() - if err != nil { - return fmt.Errorf("Unable to gob RefreshDate") - } - - session.Values["RefreshDate_Discord"] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) - default: - return fmt.Errorf("Login without a valid auth method") + gobbed, err := auth.Date.GobEncode() + if err != nil { + return fmt.Errorf("Unable to gob Date") } + session.Values["Date_"+string(auth.Type)] = fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) + return session.Save(r, w) } func delSession(session *sessions.Session, w http.ResponseWriter, r *http.Request) error { delete(session.Values, "UserId") - delete(session.Values, "PassDate") - delete(session.Values, "RefreshDate_Discord") - delete(session.Values, "RefreshDate_Twitch") - delete(session.Values, "RefreshDate_Patreon") + delete(session.Values, "Date_Local") + delete(session.Values, "Date_Discord") + delete(session.Values, "Date_Twitch") + delete(session.Values, "Date_Patreon") return session.Save(r, w) } @@ -112,10 +85,10 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. } // I am sorry - CptPie - passDate, _ := session.Values["PassDate"].(string) - refreshTwitch, _ := session.Values["RefreshDate_Twitch"].(string) - refreshDiscord, _ := session.Values["RefreshDate_Discord"].(string) - refreshPatreon, _ := session.Values["RefreshDate_Patreon"].(string) + passDate, _ := session.Values["Date_Local"].(string) + refreshTwitch, _ := session.Values["Date_Twitch"].(string) + refreshDiscord, _ := session.Values["Date_Discord"].(string) + refreshPatreon, _ := session.Values["Date_Patreon"].(string) if passDate != "" { localAuth, err := user.GetAuthMethod(common.AUTH_LOCAL) @@ -125,10 +98,10 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. return nil } - gobbed, err := localAuth.PassDate.GobEncode() + gobbed, err := localAuth.Date.GobEncode() if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != passDate { - s.l.Info("User's PassDate did not match stored value") + s.l.Info("User's Date_Local did not match stored value") err = delSession(session, w, r) if err != nil { s.l.Error("Unable to delete cookie: %v", err) @@ -143,10 +116,10 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. return nil } - gobbed, err := twitchAuth.RefreshDate.GobEncode() + gobbed, err := twitchAuth.Date.GobEncode() if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshTwitch { - s.l.Info("User's RefreshDate did not match stored value") + s.l.Info("User's Date_Twitch did not match stored value") err = delSession(session, w, r) if err != nil { s.l.Error("Unable to delete cookie: %v", err) @@ -161,10 +134,10 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. return nil } - gobbed, err := discordAuth.RefreshDate.GobEncode() + gobbed, err := discordAuth.Date.GobEncode() if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshDiscord { - s.l.Info("User's RefreshDate did not match stored value") + s.l.Info("User's Date_Discord did not match stored value") err = delSession(session, w, r) if err != nil { s.l.Error("Unable to delete cookie: %v", err) @@ -179,10 +152,10 @@ func (s *Server) getSessionUser(w http.ResponseWriter, r *http.Request) *common. return nil } - gobbed, err := patreonAuth.RefreshDate.GobEncode() + gobbed, err := patreonAuth.Date.GobEncode() if err != nil || fmt.Sprintf("%X", sha256.Sum256([]byte(gobbed))) != refreshPatreon { - s.l.Info("User's RefreshDate did not match stored value") + s.l.Info("User's Date_Patreon did not match stored value") err = delSession(session, w, r) if err != nil { s.l.Error("Unable to delete cookie: %v", err) diff --git a/user.go b/user.go index 5e351c1..32f7b68 100644 --- a/user.go +++ b/user.go @@ -181,7 +181,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { // Change pass data.SuccessMessage = "Password successfully changed" localAuth.Password = s.hashPassword(newPass1_raw) - localAuth.PassDate = time.Now() + localAuth.Date = time.Now() if err = s.data.UpdateAuthMethod(localAuth); err != nil { s.l.Error("Unable to save User with new password:", err) @@ -189,7 +189,7 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { return } - s.l.Info("new PassDate: %s", localAuth.PassDate) + s.l.Info("new Date_Local: %s", localAuth.Date) err = s.login(user, common.AUTH_LOCAL, w, r) if err != nil { s.l.Error("Unable to login to session:", err) @@ -226,8 +226,8 @@ func (s *Server) handlerUser(w http.ResponseWriter, r *http.Request) { // Change pass data.SuccessMessage = "Password successfully set" localAuth.Password = s.hashPassword(pass1_raw) - localAuth.PassDate = time.Now() - s.l.Info("new PassDate: %s", localAuth.PassDate) + localAuth.Date = time.Now() + s.l.Info("new Date_Local: %s", localAuth.Date) user, err = s.AddAuthMethodToUser(localAuth, user) @@ -567,7 +567,7 @@ func (s *Server) handlerUserNew(w http.ResponseWriter, r *http.Request) { auth := &common.AuthMethod{ Type: common.AUTH_LOCAL, Password: s.hashPassword(pw1), - PassDate: time.Now(), + Date: time.Now(), } if err != nil { From 0f46a3606b32ec3ad76121f01964df09ec8786c4 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 30 Jan 2021 23:10:34 +0100 Subject: [PATCH 66/67] Added a new Error struct for usage in the GetUsersWithAuth function --- admin.go | 25 +++++++++++++++---------- common/error.go | 13 +++++++++++++ data/json.go | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 common/error.go diff --git a/admin.go b/admin.go index a0d850e..492cefc 100644 --- a/admin.go +++ b/admin.go @@ -558,20 +558,25 @@ func (s *Server) handlerAdminConfig(w http.ResponseWriter, r *http.Request) { data.ErrorMessage = append(data.ErrorMessage, "To enable patreon signup you need to also enable patreon Oauth (and fill the token/secret)") } - // i intentionally ignore the errors here since they also idicate that no user was found - users, _ := s.data.GetUsersWithAuth(common.AUTH_TWITCH, true) - if (len(users) != 0) && !twitchOauth { - data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Twitch Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + users, err := s.data.GetUsersWithAuth(common.AUTH_TWITCH, true) + if err, ok := err.(*common.ErrNoUsersFound); !ok || err == nil { + if (len(users) != 0) && !twitchOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Twitch Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } } - users, _ = s.data.GetUsersWithAuth(common.AUTH_PATREON, true) - if (len(users) != 0) && !patreonOauth { - data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Patreon Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + users, err = s.data.GetUsersWithAuth(common.AUTH_PATREON, true) + if err, ok := err.(*common.ErrNoUsersFound); !ok || err == nil { + if (len(users) != 0) && !patreonOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Patreon Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } } - users, _ = s.data.GetUsersWithAuth(common.AUTH_DISCORD, true) - if (len(users) != 0) && !discordOauth { - data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Discord Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + users, err = s.data.GetUsersWithAuth(common.AUTH_DISCORD, true) + if err, ok := err.(*common.ErrNoUsersFound); !ok || err == nil { + if (len(users) != 0) && !discordOauth { + data.ErrorMessage = append(data.ErrorMessage, fmt.Sprintf("Disabling Discord Oauth would cause %d users to be unable to login since they only have this auth method associated.", len(users))) + } } if err := s.executeTemplate(w, "adminConfig", data); err != nil { diff --git a/common/error.go b/common/error.go new file mode 100644 index 0000000..bf9131b --- /dev/null +++ b/common/error.go @@ -0,0 +1,13 @@ +package common + +import ( + "fmt" +) + +type ErrNoUsersFound struct { + Auth AuthType +} + +func (e *ErrNoUsersFound) Error() string { + return fmt.Sprintf("No users found with AuthType %v", e.Auth) +} diff --git a/data/json.go b/data/json.go index 6a94d05..5db6d78 100644 --- a/data/json.go +++ b/data/json.go @@ -1604,7 +1604,7 @@ func (j *jsonConnector) GetUsersWithAuth(auth common.AuthType, exclusive bool) ( } } if len(res) == 0 { - return nil, fmt.Errorf("No users with authmethod %s found.", auth) + return nil, &common.ErrNoUsersFound{auth} } return res, nil } From 99a33fcb348633376a30abcc7012e84b28f9bd80 Mon Sep 17 00:00:00 2001 From: CptPie <23438606+CptPie@users.noreply.github.com> Date: Sat, 30 Jan 2021 23:59:26 +0100 Subject: [PATCH 67/67] Tidied up link cleanup code according to zorchs review --- common/link.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/common/link.go b/common/link.go index 9d04bf6..68f810c 100644 --- a/common/link.go +++ b/common/link.go @@ -47,9 +47,8 @@ var re_validLink = *regexp.MustCompile(`[a-zA-Z0-9:._\+]{1,256}\.[a-zA-Z0-9()]{1 func (l *Link) validateLink() error { url := l.Url + url = cleanupLink(url) if re_validLink.MatchString(url) { - url = stripRefFromLink(url) - url = stripMobileFromLink(url) if len(url) <= 8 { // lets be stupid when the link is too short @@ -69,18 +68,20 @@ func (l *Link) validateLink() error { return fmt.Errorf("Invalid link: %v", l.Url) } -func stripRefFromLink(link string) string { +var replacements = map[string]string{ + "m.imdb.com": "imdb.com", +} + +func cleanupLink(link string) string { idx := strings.Index(link, "/?") if idx != -1 { - return link[:idx] + link = link[:idx] + } + for from, to := range replacements { + link = strings.Replace(link, from, to, 1) } - return link -} -// for now we just replace it for imdb, if we need others we need to adapt that -// is it stupid, YES, does it work, i guess? do i have a better idea, no -func stripMobileFromLink(link string) string { - return strings.Replace(link, "m.imdb.com", "imdb.com", 1) + return link } func (l *Link) determineLinkType() error {