134 lines
3.5 KiB
Go
134 lines
3.5 KiB
Go
package customerauth
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPasswordRoundTrip(t *testing.T) {
|
|
h, err := HashPassword("correct horse battery staple")
|
|
if err != nil {
|
|
t.Fatalf("hash: %v", err)
|
|
}
|
|
if !VerifyPassword("correct horse battery staple", h) {
|
|
t.Fatal("correct password did not verify")
|
|
}
|
|
if VerifyPassword("wrong", h) {
|
|
t.Fatal("wrong password verified")
|
|
}
|
|
}
|
|
|
|
func TestPasswordHashesAreSalted(t *testing.T) {
|
|
a, _ := HashPassword("hunter2hunter2")
|
|
b, _ := HashPassword("hunter2hunter2")
|
|
if a == b {
|
|
t.Fatal("two hashes of the same password are identical — not salted")
|
|
}
|
|
}
|
|
|
|
func TestVerifyRejectsMalformed(t *testing.T) {
|
|
for _, bad := range []string{"", "x", "pbkdf2-sha256$abc$salt$hash", "md5$1$a$b"} {
|
|
if VerifyPassword("whatever", bad) {
|
|
t.Fatalf("malformed hash %q verified", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSessionRoundTrip(t *testing.T) {
|
|
s := NewSigner([]byte("test-secret"))
|
|
tok := s.Issue(12345, time.Hour)
|
|
id, err := s.Parse(tok)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if id != 12345 {
|
|
t.Fatalf("got id %d, want 12345", id)
|
|
}
|
|
}
|
|
|
|
func TestSessionExpired(t *testing.T) {
|
|
s := NewSigner([]byte("test-secret"))
|
|
tok := s.Issue(1, -time.Second)
|
|
if _, err := s.Parse(tok); err != ErrExpiredToken {
|
|
t.Fatalf("got %v, want ErrExpiredToken", err)
|
|
}
|
|
}
|
|
|
|
func TestSessionTamperAndWrongKey(t *testing.T) {
|
|
s := NewSigner([]byte("test-secret"))
|
|
tok := s.Issue(7, time.Hour)
|
|
if _, err := s.Parse(tok + "x"); err != ErrInvalidToken {
|
|
t.Fatalf("tampered token: got %v, want ErrInvalidToken", err)
|
|
}
|
|
other := NewSigner([]byte("different-secret"))
|
|
if _, err := other.Parse(tok); err != ErrInvalidToken {
|
|
t.Fatalf("wrong key: got %v, want ErrInvalidToken", err)
|
|
}
|
|
}
|
|
|
|
func TestStoreRegisterDuplicateAndReload(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "credentials.json")
|
|
store, err := LoadCredentialStore(path)
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
ctx := context.Background()
|
|
if err := store.Register(ctx, "User@Example.com", 42, "hash1", "ts"); err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
// Duplicate (case-insensitive) is rejected.
|
|
if err := store.Register(ctx, "user@example.com", 99, "hash2", "ts"); err != ErrEmailExists {
|
|
t.Fatalf("duplicate: got %v, want ErrEmailExists", err)
|
|
}
|
|
// Reload from disk and confirm the record persisted.
|
|
reloaded, err := LoadCredentialStore(path)
|
|
if err != nil {
|
|
t.Fatalf("reload: %v", err)
|
|
}
|
|
rec, ok := reloaded.Get(ctx, "USER@EXAMPLE.COM")
|
|
if !ok {
|
|
t.Fatal("record not found after reload")
|
|
}
|
|
if rec.ProfileID != 42 || rec.Hash != "hash1" {
|
|
t.Fatalf("unexpected record: %+v", rec)
|
|
}
|
|
}
|
|
|
|
func TestStoreDelete(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "credentials.json")
|
|
store, err := LoadCredentialStore(path)
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
ctx := context.Background()
|
|
if err := store.Register(ctx, "user@example.com", 42, "hash1", "ts"); err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
|
|
// Delete
|
|
ok, err := store.Delete(ctx, "USER@example.com")
|
|
if err != nil {
|
|
t.Fatalf("delete failed: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("expected delete to report email was found")
|
|
}
|
|
|
|
// Confirm not found
|
|
if _, ok := store.Get(ctx, "user@example.com"); ok {
|
|
t.Fatal("record still exists after delete")
|
|
}
|
|
|
|
// Reload from disk and verify it's gone
|
|
reloaded, err := LoadCredentialStore(path)
|
|
if err != nil {
|
|
t.Fatalf("reload: %v", err)
|
|
}
|
|
if _, ok := reloaded.Get(ctx, "user@example.com"); ok {
|
|
t.Fatal("record still exists after reload")
|
|
}
|
|
}
|
|
|