implemented basic userID generation
This commit is contained in:
parent
bec64827d1
commit
a9d8aa2591
@ -22,10 +22,11 @@ import (
|
||||
// Params are used to instantiate a new API instance. All fields are required
|
||||
// unless otherwise noted.
|
||||
type Params struct {
|
||||
Logger *mlog.Logger
|
||||
PowManager pow.Manager
|
||||
MailingList mailinglist.MailingList
|
||||
GlobalRoom chat.Room
|
||||
Logger *mlog.Logger
|
||||
PowManager pow.Manager
|
||||
MailingList mailinglist.MailingList
|
||||
GlobalRoom chat.Room
|
||||
UserIDCalculator chat.UserIDCalculator
|
||||
|
||||
// ListenProto and ListenAddr are passed into net.Listen to create the
|
||||
// API's listener. Both "tcp" and "unix" protocols are explicitly
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
|
||||
)
|
||||
@ -39,3 +41,48 @@ func (a *api) chatHistoryHandler() http.Handler {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (a *api) getUserID(r *http.Request) (chat.UserID, error) {
|
||||
name := r.PostFormValue("name")
|
||||
if l := len(name); l == 0 {
|
||||
return chat.UserID{}, errors.New("name is required")
|
||||
} else if l > 16 {
|
||||
return chat.UserID{}, errors.New("name too long")
|
||||
}
|
||||
|
||||
nameClean := strings.Map(func(r rune) rune {
|
||||
if !unicode.IsPrint(r) {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, name)
|
||||
|
||||
if nameClean != name {
|
||||
return chat.UserID{}, errors.New("name contains invalid characters")
|
||||
}
|
||||
|
||||
password := r.PostFormValue("password")
|
||||
if l := len(password); l == 0 {
|
||||
return chat.UserID{}, errors.New("password is required")
|
||||
} else if l > 128 {
|
||||
return chat.UserID{}, errors.New("password too long")
|
||||
}
|
||||
|
||||
return a.params.UserIDCalculator.Calculate(name, password), nil
|
||||
}
|
||||
|
||||
func (a *api) getUserIDHandler() http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
userID, err := a.getUserID(r)
|
||||
if err != nil {
|
||||
badRequest(rw, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(rw, r, struct {
|
||||
UserID chat.UserID `json:"userID"`
|
||||
}{
|
||||
UserID: userID,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -29,17 +29,6 @@ var (
|
||||
errInvalidMessageID = ErrInvalidArg{Err: errors.New("invalid Message ID")}
|
||||
)
|
||||
|
||||
// UserID uniquely identifies an individual user who has posted a message in a
|
||||
// Room.
|
||||
type UserID struct {
|
||||
|
||||
// Name will be the user's chosen display name.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Hash will be a hex string generated from a secret only the user knows.
|
||||
Hash string `json:"id"`
|
||||
}
|
||||
|
||||
// Message describes a message which has been posted to a Room.
|
||||
type Message struct {
|
||||
ID string `json:"id"`
|
||||
|
68
srv/chat/user.go
Normal file
68
srv/chat/user.go
Normal file
@ -0,0 +1,68 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
// UserID uniquely identifies an individual user who has posted a message in a
|
||||
// Room.
|
||||
type UserID struct {
|
||||
|
||||
// Name will be the user's chosen display name.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Hash will be a hex string generated from a secret only the user knows.
|
||||
Hash string `json:"id"`
|
||||
}
|
||||
|
||||
// UserIDCalculator is used to calculate UserIDs.
|
||||
type UserIDCalculator struct {
|
||||
|
||||
// Secret is used when calculating UserID Hash salts.
|
||||
Secret []byte
|
||||
|
||||
// TimeCost, MemoryCost, and Threads are used as inputs to the Argon2id
|
||||
// algorithm which is used to generate the Hash.
|
||||
TimeCost, MemoryCost uint32
|
||||
Threads uint8
|
||||
|
||||
// HashLen specifies the number of bytes the Hash should be.
|
||||
HashLen uint32
|
||||
|
||||
// Lock, if set, forces concurrent Calculate calls to occur sequentially.
|
||||
Lock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewUserIDCalculator returns a UserIDCalculator with sane defaults.
|
||||
func NewUserIDCalculator(secret []byte) UserIDCalculator {
|
||||
return UserIDCalculator{
|
||||
Secret: secret,
|
||||
TimeCost: 15,
|
||||
MemoryCost: 128 * 1024,
|
||||
Threads: 2,
|
||||
HashLen: 16,
|
||||
Lock: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate accepts a name and password and returns the calculated UserID.
|
||||
func (c UserIDCalculator) Calculate(name, password string) UserID {
|
||||
|
||||
input := fmt.Sprintf("%q:%q", name, password)
|
||||
|
||||
hashB := argon2.IDKey(
|
||||
[]byte(input),
|
||||
c.Secret, // salt
|
||||
c.TimeCost, c.MemoryCost, c.Threads,
|
||||
c.HashLen,
|
||||
)
|
||||
|
||||
return UserID{
|
||||
Name: name,
|
||||
Hash: hex.EncodeToString(hashB),
|
||||
}
|
||||
}
|
26
srv/chat/user_test.go
Normal file
26
srv/chat/user_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserIDCalculator(t *testing.T) {
|
||||
|
||||
const name, password = "name", "password"
|
||||
|
||||
c := NewUserIDCalculator([]byte("foo"))
|
||||
|
||||
// calculating with same params twice should result in same UserID
|
||||
userID := c.Calculate(name, password)
|
||||
assert.Equal(t, userID, c.Calculate(name, password))
|
||||
|
||||
// changing either name or password should result in a different Hash
|
||||
assert.NotEqual(t, userID.Hash, c.Calculate(name+"!", password).Hash)
|
||||
assert.NotEqual(t, userID.Hash, c.Calculate(name, password+"!").Hash)
|
||||
|
||||
// changing the secret should change the UserID
|
||||
c.Secret = []byte("bar")
|
||||
assert.NotEqual(t, userID, c.Calculate(name, password))
|
||||
}
|
27
srv/cmd/userid-calc-cli/main.go
Normal file
27
srv/cmd/userid-calc-cli/main.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
secret := flag.String("secret", "", "Secret to use when calculating UserIDs")
|
||||
name := flag.String("name", "", "")
|
||||
password := flag.String("password", "", "")
|
||||
flag.Parse()
|
||||
|
||||
calc := chat.NewUserIDCalculator([]byte(*secret))
|
||||
userID := calc.Calculate(*name, *password)
|
||||
|
||||
b, err := json.Marshal(userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
}
|
BIN
srv/cmd/userid-calc-cli/userid-calc-cli
Executable file
BIN
srv/cmd/userid-calc-cli/userid-calc-cli
Executable file
Binary file not shown.
@ -13,4 +13,5 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tilinna/clock v1.1.0
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
)
|
||||
|
@ -176,6 +176,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -187,6 +189,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -201,9 +204,15 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
Loading…
Reference in New Issue
Block a user