From a9d8aa2591cd03fe1a9da72bab8d311e4840e8f1 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 18 Aug 2021 18:13:18 -0600 Subject: implemented basic userID generation --- srv/chat/chat.go | 11 --------- srv/chat/user.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ srv/chat/user_test.go | 26 ++++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 srv/chat/user.go create mode 100644 srv/chat/user_test.go (limited to 'srv/chat') diff --git a/srv/chat/chat.go b/srv/chat/chat.go index 44449cd..ae305ac 100644 --- a/srv/chat/chat.go +++ b/srv/chat/chat.go @@ -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"` diff --git a/srv/chat/user.go b/srv/chat/user.go new file mode 100644 index 0000000..1279a45 --- /dev/null +++ b/srv/chat/user.go @@ -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), + } +} diff --git a/srv/chat/user_test.go b/srv/chat/user_test.go new file mode 100644 index 0000000..2169cde --- /dev/null +++ b/srv/chat/user_test.go @@ -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)) +} -- cgit v1.2.3