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.
This commit is contained in:
Zorchenhimer 2019-03-22 21:39:55 -04:00
parent bdfa75f8bc
commit 82149cda0c
6 changed files with 263 additions and 3 deletions

View File

@ -241,6 +241,19 @@ var commands = &CommandControl{
return "" 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{ admin: map[string]Command{
@ -291,6 +304,62 @@ var commands = &CommandControl{
return "Single use password: " + password 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"
}
},
},
}, },
} }

View File

@ -29,20 +29,23 @@ var (
CNBan ChatCommandNames = []string{"ban"} CNBan ChatCommandNames = []string{"ban"}
CNUnban ChatCommandNames = []string{"unban"} CNUnban ChatCommandNames = []string{"unban"}
CNPurge ChatCommandNames = []string{"purge"} CNPurge ChatCommandNames = []string{"purge"}
CNPin ChatCommandNames = []string{"pin", "password"}
// Admin Commands // Admin Commands
CNMod ChatCommandNames = []string{"mod"} CNMod ChatCommandNames = []string{"mod"}
CNReloadPlayer ChatCommandNames = []string{"reloadplayer"} CNReloadPlayer ChatCommandNames = []string{"reloadplayer"}
CNReloadEmotes ChatCommandNames = []string{"reloademotes"} CNReloadEmotes ChatCommandNames = []string{"reloademotes"}
CNModpass ChatCommandNames = []string{"modpass"} CNModpass ChatCommandNames = []string{"modpass"}
CNNewPin ChatCommandNames = []string{"newpin", "newpassword"}
CNRoomAccess ChatCommandNames = []string{"changeaccess", "hodor"}
) )
var ChatCommands = []ChatCommandNames{ var ChatCommands = []ChatCommandNames{
// User // User
CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers, CNNick, CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers, CNNick,
// Mod // Mod
CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, CNPurge, CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, CNPurge, CNPin,
// Admin // Admin
CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, CNRoomAccess,
} }
func GetFullChatCommand(c string) string { func GetFullChatCommand(c string) string {

View File

@ -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) { func handleHelpTemplate(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("./static/base.html", "./static/help.html") t, err := template.ParseFiles("./static/base.html", "./static/help.html")
if err != nil { 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) { 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") t, err := template.ParseFiles("./static/base.html", "./static/main.html")
if err != nil { if err != nil {
fmt.Printf("Error parsing template file, %v\n", err) fmt.Printf("Error parsing template file, %v\n", err)

View File

@ -51,6 +51,8 @@ func main() {
fmt.Println("Stream key: ", settings.GetStreamKey()) fmt.Println("Stream key: ", settings.GetStreamKey())
fmt.Println("Admin password: ", settings.AdminPassword) fmt.Println("Admin password: ", settings.AdminPassword)
fmt.Println("RoomAccess: ", settings.RoomAccess)
fmt.Println("RoomAccessPin: ", settings.RoomAccessPin)
fmt.Println("Listen and serve ", addr) fmt.Println("Listen and serve ", addr)
go startServer() go startServer()
@ -82,6 +84,7 @@ func startServer() {
http.HandleFunc("/chat", handleIndexTemplate) http.HandleFunc("/chat", handleIndexTemplate)
http.HandleFunc("/video", handleIndexTemplate) http.HandleFunc("/video", handleIndexTemplate)
http.HandleFunc("/help", handleHelpTemplate) http.HandleFunc("/help", handleHelpTemplate)
http.HandleFunc("/pin", handlePin)
http.HandleFunc("/", handleDefault) http.HandleFunc("/", handleDefault)

View File

@ -6,13 +6,17 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net/http"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/gorilla/sessions"
) )
var settings *Settings var settings *Settings
var settingsMtx sync.Mutex var settingsMtx sync.Mutex
var sstore *sessions.CookieStore
type Settings struct { type Settings struct {
// Non-Saved settings // Non-Saved settings
@ -24,11 +28,22 @@ type Settings struct {
MaxMessageCount int MaxMessageCount int
TitleLength int // maximum length of the title that can be set with the /playing TitleLength int // maximum length of the title that can be set with the /playing
AdminPassword string AdminPassword string
Bans []BanInfo
StreamKey string StreamKey string
ListenAddress 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 { type BanInfo struct {
IP string IP string
Names []string Names []string
@ -49,6 +64,36 @@ func init() {
settings.TitleLength = 50 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 // Save admin password to file
if err = settings.Save(); err != nil { if err = settings.Save(); err != nil {
panic("Unable to save settings: " + err.Error()) panic("Unable to save settings: " + err.Error())
@ -174,3 +219,12 @@ func (s *Settings) GetStreamKey() string {
} }
return s.StreamKey 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
}

13
static/thedoor.html Normal file
View File

@ -0,0 +1,13 @@
{{define "header"}}
{{end}}
{{define "body"}}
<div>
{{if .Error}}<div>{{.Error}}</div>{{end}}
<form action="/" method="post">
<input type="text" name="txtInput" /><br />
<input type="submit" value="{{.SubmitText}}" />
</form>
</div>
{{end}}