Add single-use mod passwords

Single-use moderator passwords can only be generated by an admin with
the /modpass command.  To redeem the password and gain moderator
privileges, a user just needs to call /auth with the password.

The passwords are generated using the same function as the admin
password.  Additionally, generating passwords now uses crypto/rand
instead of math/rand.

Resolves #15
This commit is contained in:
Zorchenhimer 2019-03-13 16:47:32 -04:00
parent 5516313c79
commit 1cd490b04a
3 changed files with 90 additions and 11 deletions

View File

@ -65,15 +65,21 @@ func init() {
pw := html.UnescapeString(strings.Join(args, " "))
//fmt.Printf("/auth from %s. expecting %q [%X], received %q [%X]\n", cl.name, settings.AdminPassword, settings.AdminPassword, pw, pw)
if settings.AdminPassword == pw {
cl.IsMod = true
cl.IsAdmin = true
fmt.Printf("[auth] %s used the admin password\n", cl.name)
return "Admin rights granted."
}
// Don't let on that this command exists. Not the most secure, but should be "good enough" LUL.
return "Invalid command."
if cl.belongsTo.redeemModPass(pw) {
cl.IsMod = true
fmt.Printf("[auth] %s used a mod password\n", cl.name)
return "Moderator privileges granted."
}
fmt.Printf("[auth] %s gave an invalid password\n", cl.name)
return "Invalid password."
},
},
@ -222,6 +228,14 @@ func init() {
},
},
"modpass": Command{
HelpText: "Generate a single-use mod password.",
Function: func(cl *Client, args []string) string {
password := cl.belongsTo.generateModPass()
return "Single use password: " + password
},
},
//"reloadsettings": func(cl *Client, args []string) string {
// return ""
//},

View File

@ -31,6 +31,9 @@ type ChatRoom struct {
queue chan string
playing string
playingLink string
modPasswords []string // single-use mod passwords
modPasswordsMtx sync.Mutex
}
//initializing the chatroom
@ -406,3 +409,58 @@ func (cr *ChatRoom) getClient(name string) (*Client, error) {
}
return nil, fmt.Errorf("Client with that name not found.")
}
func (cr *ChatRoom) generateModPass() string {
defer cr.modPasswordsMtx.Unlock()
cr.modPasswordsMtx.Lock()
pass, err := generatePass(time.Now().Unix())
if err != nil {
return fmt.Sprintf("Error generating moderator password: %s", err)
}
// Make sure the password is unique
for existsInSlice(cr.modPasswords, pass) {
pass, err = generatePass(time.Now().Unix())
if err != nil {
return fmt.Sprintf("Error generating moderator password: %s", err)
}
}
cr.modPasswords = append(cr.modPasswords, pass)
return pass
}
func (cr *ChatRoom) redeemModPass(pass string) bool {
if pass == "" {
return false
}
defer cr.modPasswordsMtx.Unlock()
cr.modPasswordsMtx.Lock()
if existsInSlice(cr.modPasswords, pass) {
cr.modPasswords = removeFromSlice(cr.modPasswords, pass)
return true
}
return false
}
func removeFromSlice(slice []string, needle string) []string {
slc := []string{}
for _, item := range slice {
if item != needle {
slc = append(slc, item)
}
}
return slc
}
func existsInSlice(slice []string, needle string) bool {
for _, item := range slice {
if item == needle {
return true
}
}
return false
}

View File

@ -1,10 +1,11 @@
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"math/big"
"strings"
"sync"
"time"
@ -62,23 +63,29 @@ func LoadSettings(filename string) (*Settings, error) {
if s.MaxMessageCount == 0 {
s.MaxMessageCount = 300
} else if s.MaxMessageCount < 0 {
return s, fmt.Errorf("the MaxMessageCount value must be greater than 0, given %d", s.MaxMessageCount)
return s, fmt.Errorf("MaxMessageCount value must be greater than 0, given %d", s.MaxMessageCount)
}
s.AdminPassword = generateAdminPass(time.Now().Unix())
s.AdminPassword, err = generatePass(time.Now().Unix())
if err != nil {
return nil, fmt.Errorf("Unable to generate admin password: %s", err)
}
fmt.Printf("Settings reloaded. New admin password: %s\n", s.AdminPassword)
return s, nil
}
func generateAdminPass(seed int64) string {
func generatePass(seed int64) (string, error) {
out := ""
r := rand.New(rand.NewSource(seed))
//for i := 0; i < 20; i++ {
for len(out) < 20 {
out = fmt.Sprintf("%s%X", out, r.Int31())
num, err := rand.Int(rand.Reader, big.NewInt(int64(15)))
if err != nil {
return "", err
}
out = fmt.Sprintf("%s%X", out, num)
}
return out
return out, nil
}
func (s *Settings) Save() error {