Feature(Go): Web auth, WebSocket signaling and live device list (Phase 3)
This commit is contained in:
116
server/go/wsauth/wsauth_test.go
Normal file
116
server/go/wsauth/wsauth_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package wsauth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSHA256Vector(t *testing.T) {
|
||||
// Known vector — keeps us honest against accidental algorithm changes.
|
||||
got := ComputeSHA256("abc")
|
||||
want := "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
|
||||
if got != want {
|
||||
t.Fatalf("SHA256(abc): got %s want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginRoundTripAdminEmptySalt(t *testing.T) {
|
||||
a := New()
|
||||
a.AddAdminFromPlainPassword("admin", "hunter2")
|
||||
|
||||
salt, ok := a.GetSalt("admin")
|
||||
if !ok || salt != "" {
|
||||
t.Fatalf("admin salt: ok=%v salt=%q", ok, salt)
|
||||
}
|
||||
|
||||
// Simulate the browser: nonce = "abc123", response = SHA256(passwordHash + nonce)
|
||||
nonce := "abc123"
|
||||
passwordHash := ComputeSHA256("hunter2")
|
||||
response := ComputeSHA256(passwordHash + nonce)
|
||||
|
||||
token, role, err := a.VerifyLogin("admin", response, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("VerifyLogin: %v", err)
|
||||
}
|
||||
if role != "admin" {
|
||||
t.Fatalf("role: got %q want admin", role)
|
||||
}
|
||||
if len(token) != 2*tokenBytes {
|
||||
t.Fatalf("token length: got %d want %d", len(token), 2*tokenBytes)
|
||||
}
|
||||
|
||||
sess, err := a.ValidateToken(token)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateToken: %v", err)
|
||||
}
|
||||
if sess.Username != "admin" || sess.Role != "admin" {
|
||||
t.Fatalf("session: %+v", sess)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginRoundTripViewerWithSalt(t *testing.T) {
|
||||
a := New()
|
||||
salt, _ := NewSalt()
|
||||
a.AddUser(User{
|
||||
Username: "alice",
|
||||
PasswordHash: HashPassword("p@ss", salt),
|
||||
Salt: salt,
|
||||
Role: "viewer",
|
||||
})
|
||||
|
||||
gotSalt, ok := a.GetSalt("alice")
|
||||
if !ok || gotSalt != salt {
|
||||
t.Fatalf("salt: ok=%v got=%q want=%q", ok, gotSalt, salt)
|
||||
}
|
||||
|
||||
nonce, _ := NewNonce()
|
||||
response := ComputeSHA256(HashPassword("p@ss", salt) + nonce)
|
||||
_, role, err := a.VerifyLogin("alice", response, nonce)
|
||||
if err != nil || role != "viewer" {
|
||||
t.Fatalf("VerifyLogin: role=%q err=%v", role, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginRejectsWrongResponse(t *testing.T) {
|
||||
a := New()
|
||||
a.AddAdminFromPlainPassword("admin", "x")
|
||||
_, _, err := a.VerifyLogin("admin", "deadbeef", "nonce")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for bad response")
|
||||
}
|
||||
_, _, err = a.VerifyLogin("ghost", "anything", "anything")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenExpiry(t *testing.T) {
|
||||
a := New()
|
||||
a.SetTokenExpire(50 * time.Millisecond)
|
||||
a.AddAdminFromPlainPassword("admin", "x")
|
||||
nonce, _ := NewNonce()
|
||||
response := ComputeSHA256(ComputeSHA256("x") + nonce)
|
||||
token, _, err := a.VerifyLogin("admin", response, nonce)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := a.ValidateToken(token); err != nil {
|
||||
t.Fatalf("fresh token should validate: %v", err)
|
||||
}
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
if _, err := a.ValidateToken(token); err == nil {
|
||||
t.Fatal("expired token should not validate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevoke(t *testing.T) {
|
||||
a := New()
|
||||
a.AddAdminFromPlainPassword("admin", "x")
|
||||
nonce, _ := NewNonce()
|
||||
response := ComputeSHA256(ComputeSHA256("x") + nonce)
|
||||
token, _, _ := a.VerifyLogin("admin", response, nonce)
|
||||
a.RevokeToken(token)
|
||||
if _, err := a.ValidateToken(token); err == nil {
|
||||
t.Fatal("revoked token should not validate")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user