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 ""
},
},
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"
}
},
},
},
}

View File

@ -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 {

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) {
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)

View File

@ -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)

View File

@ -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
}

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}}