summaryrefslogtreecommitdiff
path: root/srv/chat
diff options
context:
space:
mode:
Diffstat (limited to 'srv/chat')
-rw-r--r--srv/chat/chat.go11
-rw-r--r--srv/chat/user.go68
-rw-r--r--srv/chat/user_test.go26
3 files changed, 94 insertions, 11 deletions
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))
+}