From 82149cda0c4a992b891f7efe8216712e602c1b49 Mon Sep 17 00:00:00 2001 From: Zorchenhimer Date: Fri, 22 Mar 2019 21:39:55 -0400 Subject: [PATCH] Start adding room access restrictions So far only PIN and Open modes are implemented. It uses a session cookie to store the validity of the pin/password. The "Enter pin" page has some unreadable messages on it right now, but it kinda works. --- chatcommands.go | 69 ++++++++++++++++++++++++ common/chatcommands.go | 7 ++- handlers.go | 118 +++++++++++++++++++++++++++++++++++++++++ main.go | 3 ++ settings.go | 56 ++++++++++++++++++- static/thedoor.html | 13 +++++ 6 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 static/thedoor.html diff --git a/chatcommands.go b/chatcommands.go index 1f86f41..f043355 100644 --- a/chatcommands.go +++ b/chatcommands.go @@ -241,6 +241,19 @@ var commands = &CommandControl{ return "" }, }, + + common.CNPin.String(): Command{ + HelpText: "Display the current room access type and pin/password (if applicable).", + Function: func(cl *Client, args []string) string { + switch settings.RoomAccess { + case AccessPin: + return "Room is secured via PIN. Current PIN: " + settings.RoomAccessPin + case AccessRequest: + return "Room is secured via access requests. Users must request to be granted access." + } + return "Room is open access. Anybody can join." + }, + }, }, admin: map[string]Command{ @@ -291,6 +304,62 @@ var commands = &CommandControl{ return "Single use password: " + password }, }, + + common.CNNewPin.String(): Command{ + HelpText: "Generate a room acces new pin", + Function: func(cl *Client, args []string) string { + if settings.RoomAccess != AccessPin { + return "Room is not restricted by Pin. (" + string(settings.RoomAccess) + ")" + } + + pin, err := settings.generateNewPin() + if err != nil { + return "Unable to generate new pin: " + err.Error() + } + + fmt.Println("New room access pin: ", pin) + return "New access pin: " + pin + }, + }, + + common.CNRoomAccess.String(): Command{ + HelpText: "Change the room access type.", + Function: func(cl *Client, args []string) string { + // Print current access type if no arguments given + if len(args) == 0 { + return "Current room access type: " + string(settings.RoomAccess) + } + + switch AccessMode(strings.ToLower(args[0])) { + case AccessOpen: + settings.RoomAccess = AccessOpen + return "Room access set to open" + + case AccessPin: + // A pin/password was provided, use it. + if len(args) == 2 { + settings.RoomAccessPin = args[1] + + // A pin/password was not provided, generate a new one. + } else { + _, err := settings.generateNewPin() + if err != nil { + fmt.Println("Error generating new access pin: ", err.Error()) + return "Unable to generate a new pin, access unchanged: " + err.Error() + } + } + settings.RoomAccess = AccessPin + return "Room access set to Pin: " + settings.RoomAccessPin + + case AccessRequest: + settings.RoomAccess = AccessRequest + return "Room access set to request. WARNING: this isn't implemented yet." + + default: + return "Invalid access mode" + } + }, + }, }, } diff --git a/common/chatcommands.go b/common/chatcommands.go index c44f689..d928b43 100644 --- a/common/chatcommands.go +++ b/common/chatcommands.go @@ -29,20 +29,23 @@ var ( CNBan ChatCommandNames = []string{"ban"} CNUnban ChatCommandNames = []string{"unban"} CNPurge ChatCommandNames = []string{"purge"} + CNPin ChatCommandNames = []string{"pin", "password"} // Admin Commands CNMod ChatCommandNames = []string{"mod"} CNReloadPlayer ChatCommandNames = []string{"reloadplayer"} CNReloadEmotes ChatCommandNames = []string{"reloademotes"} CNModpass ChatCommandNames = []string{"modpass"} + CNNewPin ChatCommandNames = []string{"newpin", "newpassword"} + CNRoomAccess ChatCommandNames = []string{"changeaccess", "hodor"} ) var ChatCommands = []ChatCommandNames{ // User CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers, CNNick, // Mod - CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, CNPurge, + CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, CNPurge, CNPin, // Admin - CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, + CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, CNRoomAccess, } func GetFullChatCommand(c string) string { diff --git a/handlers.go b/handlers.go index 2589c0a..ae9aa93 100644 --- a/handlers.go +++ b/handlers.go @@ -153,6 +153,94 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { }() } +// returns if it's OK to proceed +func checkRoomAccess(w http.ResponseWriter, r *http.Request) bool { + session, err := sstore.Get(r, "moviesession") + if err != nil { + fmt.Printf("Unable to get session for client %s: %v\n", r.RemoteAddr, err) + http.Error(w, "Unable to get session data", http.StatusInternalServerError) + return false + } + + if settings.RoomAccess == AccessPin { + pin := session.Values["pin"] + // No pin found in session + if pin == nil || len(pin.(string)) == 0 { + if r.Method == "POST" { + // Check for correct pin + err = r.ParseForm() + if err != nil { + fmt.Printf("Error parsing form") + http.Error(w, "Unable to get session data", http.StatusInternalServerError) + } + + postPin := r.Form.Get("txtInput") + fmt.Printf("Received pin: %s\n", postPin) + if postPin == settings.RoomAccessPin { + // Pin is correct. Save it to session and return true. + session.Values["pin"] = settings.RoomAccessPin + session.Save(r, w) + return true + } + // Pin is incorrect. + handlePinTemplate(w, r, "Incorrect PIN") + return false + } + // nope. display pin entry and return + handlePinTemplate(w, r, "") + return false + } + + // Pin found in session, but it has changed since last time. + if pin.(string) != settings.RoomAccessPin { + // Clear out the old pin. + session.Values["pin"] = nil + session.Save(r, w) + + // Prompt for new one. + handlePinTemplate(w, r, "Pin has changed. Enter new PIN.") + return false + } + + // Correct pin found in session + return true + } + + // TODO: this. + if settings.RoomAccess == AccessRequest { + http.Error(w, "Requesting access not implemented yet", http.StatusNotImplemented) + return false + } + + // Room is open. + return true +} + +func handlePinTemplate(w http.ResponseWriter, r *http.Request, errorMessage string) { + t, err := template.ParseFiles("./static/base.html", "./static/thedoor.html") + if err != nil { + fmt.Printf("Error parsing template file: %v", err) + return + } + + type Data struct { + Title string + SubmitText string + Error string + } + + data := Data{ + Title: "Enter Pin", + SubmitText: "Submit Pin", + Error: errorMessage, + } + + err = t.Execute(w, data) + if err != nil { + fmt.Printf("Error executing file, %v", err) + } +} + func handleHelpTemplate(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("./static/base.html", "./static/help.html") if err != nil { @@ -186,7 +274,37 @@ func handleHelpTemplate(w http.ResponseWriter, r *http.Request) { } } +func handlePin(w http.ResponseWriter, r *http.Request) { + session, err := sstore.Get(r, "moviesession") + if err != nil { + fmt.Printf("Unable to get session: %v\n", err) + } + + val := session.Values["pin"] + if val == nil { + session.Values["pin"] = "1234" + err := session.Save(r, w) + if err != nil { + fmt.Fprintf(w, "unable to save session: %v", err) + } + fmt.Fprint(w, "Pin was not set") + fmt.Println("pin was not set") + } else { + fmt.Fprintf(w, "pin set: %v", val) + fmt.Printf("pin is set: %v\n", val) + } +} + func handleIndexTemplate(w http.ResponseWriter, r *http.Request) { + fmt.Printf("RoomAcces: %s\n", settings.RoomAccess) + if settings.RoomAccess != AccessOpen { + if !checkRoomAccess(w, r) { + fmt.Println("Denied access") + return + } + fmt.Println("Granted access") + } + t, err := template.ParseFiles("./static/base.html", "./static/main.html") if err != nil { fmt.Printf("Error parsing template file, %v\n", err) diff --git a/main.go b/main.go index 42c260f..121d75b 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,8 @@ func main() { fmt.Println("Stream key: ", settings.GetStreamKey()) fmt.Println("Admin password: ", settings.AdminPassword) + fmt.Println("RoomAccess: ", settings.RoomAccess) + fmt.Println("RoomAccessPin: ", settings.RoomAccessPin) fmt.Println("Listen and serve ", addr) go startServer() @@ -82,6 +84,7 @@ func startServer() { http.HandleFunc("/chat", handleIndexTemplate) http.HandleFunc("/video", handleIndexTemplate) http.HandleFunc("/help", handleHelpTemplate) + http.HandleFunc("/pin", handlePin) http.HandleFunc("/", handleDefault) diff --git a/settings.go b/settings.go index 7fa4626..28f6926 100644 --- a/settings.go +++ b/settings.go @@ -6,13 +6,17 @@ import ( "fmt" "io/ioutil" "math/big" + "net/http" "strings" "sync" "time" + + "github.com/gorilla/sessions" ) var settings *Settings var settingsMtx sync.Mutex +var sstore *sessions.CookieStore type Settings struct { // Non-Saved settings @@ -24,11 +28,22 @@ type Settings struct { MaxMessageCount int TitleLength int // maximum length of the title that can be set with the /playing AdminPassword string + Bans []BanInfo StreamKey string ListenAddress string - Bans []BanInfo + SessionKey string // key for session data + RoomAccess AccessMode + RoomAccessPin string // auto generate this, } +type AccessMode string + +const ( + AccessOpen AccessMode = "open" + AccessPin AccessMode = "pin" + AccessRequest AccessMode = "request" +) + type BanInfo struct { IP string Names []string @@ -49,6 +64,36 @@ func init() { settings.TitleLength = 50 } + // Is this a good way to do this? Probably not... + if len(settings.SessionKey) == 0 { + out := "" + large := big.NewInt(int64(1 << 60)) + large = large.Add(large, large) + for len(out) < 50 { + num, err := rand.Int(rand.Reader, large) + if err != nil { + panic("Error generating session key: " + err.Error()) + } + out = fmt.Sprintf("%s%X", out, num) + } + settings.SessionKey = out + } + + if len(settings.RoomAccess) == 0 { + settings.RoomAccess = AccessOpen + } + + if settings.RoomAccess != AccessOpen && len(settings.RoomAccessPin) == 0 { + settings.RoomAccessPin = "1234" + } + + sstore = sessions.NewCookieStore([]byte(settings.SessionKey)) + sstore.Options = &sessions.Options{ + Path: "/", + MaxAge: 60 * 60 * 24, // one day + SameSite: http.SameSiteStrictMode, + } + // Save admin password to file if err = settings.Save(); err != nil { panic("Unable to save settings: " + err.Error()) @@ -174,3 +219,12 @@ func (s *Settings) GetStreamKey() string { } return s.StreamKey } + +func (s *Settings) generateNewPin() (string, error) { + num, err := rand.Int(rand.Reader, big.NewInt(int64(9999))) + if err != nil { + return "", err + } + settings.RoomAccessPin = fmt.Sprintf("%04d", num) + return settings.RoomAccessPin, nil +} diff --git a/static/thedoor.html b/static/thedoor.html new file mode 100644 index 0000000..9051a8a --- /dev/null +++ b/static/thedoor.html @@ -0,0 +1,13 @@ +{{define "header"}} + +{{end}} + +{{define "body"}} +
+ {{if .Error}}
{{.Error}}
{{end}} +
+
+ +
+
+{{end}}