Allow one registration per IP address
This commit is contained in:
parent
ef40f1443a
commit
c17a214516
12 changed files with 89 additions and 30 deletions
|
@ -1,6 +1,7 @@
|
|||
package bgammon
|
||||
|
||||
type Client interface {
|
||||
Address() string
|
||||
HandleReadWrite()
|
||||
Write(message []byte)
|
||||
Terminate(reason string)
|
||||
|
|
|
@ -22,6 +22,7 @@ func main() {
|
|||
mailServer string
|
||||
passwordSalt string
|
||||
resetSalt string
|
||||
ipSalt string
|
||||
verbose bool
|
||||
debug int
|
||||
debugCommands bool
|
||||
|
@ -48,6 +49,7 @@ func main() {
|
|||
|
||||
passwordSalt = os.Getenv("BGAMMON_SALT_PASSWORD")
|
||||
resetSalt = os.Getenv("BGAMMON_SALT_RESET")
|
||||
ipSalt = os.Getenv("BGAMMON_SALT_IP")
|
||||
|
||||
if rollStatistics {
|
||||
printRollStatistics()
|
||||
|
@ -64,7 +66,7 @@ func main() {
|
|||
}()
|
||||
}
|
||||
|
||||
s := server.NewServer(tz, dataSource, mailServer, passwordSalt, resetSalt, false, verbose || debug > 0, debugCommands)
|
||||
s := server.NewServer(tz, dataSource, mailServer, passwordSalt, resetSalt, ipSalt, false, verbose || debug > 0, debugCommands)
|
||||
if tcpAddress != "" {
|
||||
s.Listen("tcp", tcpAddress)
|
||||
}
|
||||
|
|
8
go.mod
8
go.mod
|
@ -11,7 +11,8 @@ require (
|
|||
github.com/jackc/pgx/v5 v5.6.0
|
||||
github.com/jlouis/glicko2 v1.0.0
|
||||
github.com/matcornic/hermes/v2 v2.1.0
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/text v0.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -37,7 +38,6 @@ require (
|
|||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/vanng822/css v1.0.1 // indirect
|
||||
github.com/vanng822/go-premailer v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
)
|
||||
|
|
16
go.sum
16
go.sum
|
@ -103,8 +103,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -117,8 +117,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -135,8 +135,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
@ -152,8 +152,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
|
|
@ -15,6 +15,7 @@ var _ bgammon.Client = &socketClient{}
|
|||
|
||||
type socketClient struct {
|
||||
conn net.Conn
|
||||
address string
|
||||
events chan []byte
|
||||
commands chan<- []byte
|
||||
terminated bool
|
||||
|
@ -25,12 +26,17 @@ type socketClient struct {
|
|||
func newSocketClient(conn net.Conn, commands chan<- []byte, events chan []byte, verbose bool) *socketClient {
|
||||
return &socketClient{
|
||||
conn: conn,
|
||||
address: conn.RemoteAddr().String(),
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socketClient) Address() string {
|
||||
return c.address
|
||||
}
|
||||
|
||||
func (c *socketClient) HandleReadWrite() {
|
||||
if c.terminated {
|
||||
return
|
||||
|
|
|
@ -21,6 +21,7 @@ var _ bgammon.Client = &webSocketClient{}
|
|||
|
||||
type webSocketClient struct {
|
||||
conn *websocket.Conn
|
||||
address string
|
||||
events chan []byte
|
||||
commands chan<- []byte
|
||||
terminated bool
|
||||
|
@ -34,14 +35,24 @@ func newWebSocketClient(r *http.Request, w http.ResponseWriter, commands chan<-
|
|||
return nil
|
||||
}
|
||||
|
||||
address := r.Header.Get("X-Forwarded-For")
|
||||
if address == "" {
|
||||
address = r.RemoteAddr
|
||||
}
|
||||
|
||||
return &webSocketClient{
|
||||
conn: conn,
|
||||
address: address,
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *webSocketClient) Address() string {
|
||||
return c.address
|
||||
}
|
||||
|
||||
func (c *webSocketClient) HandleReadWrite() {
|
||||
if c.terminated {
|
||||
return
|
||||
|
|
|
@ -30,6 +30,7 @@ const databaseSchema = `
|
|||
CREATE TABLE account (
|
||||
id serial PRIMARY KEY,
|
||||
created bigint NOT NULL,
|
||||
createdip text NOT NULL,
|
||||
confirmed bigint NOT NULL DEFAULT 0,
|
||||
active bigint NOT NULL,
|
||||
reset bigint NOT NULL DEFAULT 0,
|
||||
|
@ -97,7 +98,7 @@ var (
|
|||
dbLock = &sync.Mutex{}
|
||||
)
|
||||
|
||||
var passwordArgon2id = &argon2id.Params{
|
||||
var argon2idParameters = &argon2id.Params{
|
||||
Memory: 128 * 1024,
|
||||
Iterations: 16,
|
||||
Parallelism: 4,
|
||||
|
@ -151,7 +152,7 @@ func initDB() {
|
|||
log.Println("Initialized database schema")
|
||||
}
|
||||
|
||||
func registerAccount(passwordSalt string, a *account) error {
|
||||
func registerAccount(passwordSalt string, a *account, ipHash string) error {
|
||||
dbLock.Lock()
|
||||
defer dbLock.Unlock()
|
||||
|
||||
|
@ -178,6 +179,13 @@ func registerAccount(passwordSalt string, a *account) error {
|
|||
defer tx.Commit(context.Background())
|
||||
|
||||
var result int
|
||||
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM account WHERE createdip = $1", ipHash).Scan(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if result > 0 {
|
||||
return fmt.Errorf("an account has already been registered from your IP address")
|
||||
}
|
||||
|
||||
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM account WHERE email = $1", bytes.ToLower(bytes.TrimSpace(a.email))).Scan(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -192,14 +200,14 @@ func registerAccount(passwordSalt string, a *account) error {
|
|||
return fmt.Errorf("username already in use")
|
||||
}
|
||||
|
||||
passwordHash, err := argon2id.CreateHash(string(a.password)+passwordSalt, passwordArgon2id)
|
||||
debug.FreeOSMemory() // Password hashing is memory intensive. Return memory to the OS.
|
||||
passwordHash, err := argon2id.CreateHash(string(a.password)+passwordSalt, argon2idParameters)
|
||||
debug.FreeOSMemory() // Hashing is memory intensive. Return memory to the OS.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
_, err = tx.Exec(context.Background(), "INSERT INTO account (created, active, email, username, password) VALUES ($1, $2, $3, $4, $5)", timestamp, timestamp, bytes.ToLower(bytes.TrimSpace(a.email)), bytes.ToLower(bytes.TrimSpace(a.username)), passwordHash)
|
||||
_, err = tx.Exec(context.Background(), "INSERT INTO account (created, createdip, active, email, username, password) VALUES ($1, $2, $3, $4, $5, $6)", timestamp, ipHash, timestamp, bytes.ToLower(bytes.TrimSpace(a.email)), bytes.ToLower(bytes.TrimSpace(a.username)), passwordHash)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -339,8 +347,8 @@ func confirmResetAccount(resetSalt string, passwordSalt string, id int, key stri
|
|||
|
||||
newPassword := randomAlphanumeric(7)
|
||||
|
||||
passwordHash, err := argon2id.CreateHash(newPassword+passwordSalt, passwordArgon2id)
|
||||
debug.FreeOSMemory() // Password hashing is memory intensive. Return memory to the OS.
|
||||
passwordHash, err := argon2id.CreateHash(newPassword+passwordSalt, argon2idParameters)
|
||||
debug.FreeOSMemory() // Hashing is memory intensive. Return memory to the OS.
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
@ -466,7 +474,7 @@ func loginAccount(passwordSalt string, username []byte, password []byte) (*accou
|
|||
a.muteBearOff = muteBearOff == 1
|
||||
|
||||
match, err := argon2id.ComparePasswordAndHash(string(password)+passwordSalt, string(a.password))
|
||||
debug.FreeOSMemory() // Password hashing is memory intensive. Return memory to the OS.
|
||||
debug.FreeOSMemory() // Hashing is memory intensive. Return memory to the OS.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !match {
|
||||
|
@ -519,8 +527,8 @@ func setAccountPassword(passwordSalt string, id int, password string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
passwordHash, err := argon2id.CreateHash(password+passwordSalt, passwordArgon2id)
|
||||
debug.FreeOSMemory() // Password hashing is memory intensive. Return memory to the OS.
|
||||
passwordHash, err := argon2id.CreateHash(password+passwordSalt, argon2idParameters)
|
||||
debug.FreeOSMemory() // Hashing is memory intensive. Return memory to the OS.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func testDBConnection() error {
|
|||
func initDB() {
|
||||
}
|
||||
|
||||
func registerAccount(passwordSalt string, a *account) error {
|
||||
func registerAccount(passwordSalt string, a *account, ipHash string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ type server struct {
|
|||
mailServer string
|
||||
passwordSalt string
|
||||
resetSalt string
|
||||
ipSalt string
|
||||
|
||||
tz *time.Location
|
||||
languageTags []language.Tag
|
||||
|
@ -100,7 +101,7 @@ type server struct {
|
|||
shutdownReason string
|
||||
}
|
||||
|
||||
func NewServer(tz string, dataSource string, mailServer string, passwordSalt string, resetSalt string, relayChat bool, verbose bool, allowDebug bool) *server {
|
||||
func NewServer(tz string, dataSource string, mailServer string, passwordSalt string, resetSalt string, ipSalt string, relayChat bool, verbose bool, allowDebug bool) *server {
|
||||
const bufferSize = 10
|
||||
s := &server{
|
||||
newGameIDs: make(chan int),
|
||||
|
@ -111,6 +112,7 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
|
|||
mailServer: mailServer,
|
||||
passwordSalt: passwordSalt,
|
||||
resetSalt: resetSalt,
|
||||
ipSalt: ipSalt,
|
||||
relayChat: relayChat,
|
||||
verbose: verbose,
|
||||
}
|
||||
|
|
|
@ -81,7 +81,8 @@ func (s *server) handleFirstCommand(cmd serverCommand, keyword string, params []
|
|||
username: username,
|
||||
password: password,
|
||||
}
|
||||
err := registerAccount(s.passwordSalt, a)
|
||||
ipHash := s.hashIP(cmd.client.Address())
|
||||
err := registerAccount(s.passwordSalt, a, ipHash)
|
||||
if err != nil {
|
||||
cmd.client.Terminate(fmt.Sprintf("Failed to register: %s", err))
|
||||
return
|
||||
|
@ -1501,10 +1502,10 @@ COMMANDS:
|
|||
}
|
||||
|
||||
clientGame.Turn = 1
|
||||
clientGame.Roll1 = 3
|
||||
clientGame.Roll2 = 3
|
||||
clientGame.Roll1 = 1
|
||||
clientGame.Roll2 = 2
|
||||
clientGame.Roll3 = 0
|
||||
clientGame.Variant = 0
|
||||
clientGame.Variant = 1
|
||||
clientGame.Player1.Entered = true
|
||||
clientGame.Player2.Entered = true
|
||||
clientGame.Board = []int8{0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, -3, 0, 0, -3, -6, -2, 0, 0, 0}
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
package server
|
||||
|
||||
import "log"
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func (s *server) Listen(network string, address string) {
|
||||
log.Fatal("bgammon-server was built without the 'full' tag. Only local connections are possible.")
|
||||
}
|
||||
|
||||
func (s *server) hashIP(address string) string {
|
||||
return address
|
||||
}
|
||||
|
|
|
@ -14,9 +14,14 @@ import (
|
|||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func (s *server) Listen(network string, address string) {
|
||||
if s.passwordSalt == "" || s.resetSalt == "" || s.ipSalt == "" {
|
||||
log.Fatal("error: password, reset and ip salts must be configured")
|
||||
}
|
||||
|
||||
if strings.ToLower(network) == "ws" {
|
||||
go s.listenWebSocket(address)
|
||||
return
|
||||
|
@ -341,3 +346,20 @@ func (s *server) handlePrintWildBGStats(w http.ResponseWriter, r *http.Request)
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(s.cachedStats(4))
|
||||
}
|
||||
|
||||
func (s *server) hashIP(address string) string {
|
||||
leftBracket, rightBracket := strings.IndexByte(address, '['), strings.IndexByte(address, ']')
|
||||
if leftBracket != -1 && rightBracket != -1 && rightBracket > leftBracket {
|
||||
address = address[1:rightBracket]
|
||||
} else if strings.IndexByte(address, '.') != -1 {
|
||||
colon := strings.IndexByte(address, ':')
|
||||
if colon != -1 {
|
||||
address = address[:colon]
|
||||
}
|
||||
}
|
||||
|
||||
buf := []byte(address + s.ipSalt)
|
||||
h := make([]byte, 64)
|
||||
sha3.ShakeSum256(h, buf)
|
||||
return fmt.Sprintf("%x\n", h)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue