package auth import ( "encoding/base64" "fmt" "strings" "testing" "golang.org/x/crypto/argon2" ) func TestHashVerifyRoundTrip(t *testing.T) { hash, err := HashPassword("correct horse battery staple") if err != nil { t.Fatal(err) } if !strings.HasPrefix(hash, "$argon2id$v=19$m=65536,t=3,p=2$") { t.Fatalf("hash %q does not embed the spec parameters", hash) } ok, err := VerifyPassword("correct horse battery staple", hash) if err != nil || !ok { t.Fatalf("correct password: ok=%v err=%v", ok, err) } ok, err = VerifyPassword("wrong password", hash) if err != nil || ok { t.Fatalf("wrong password: ok=%v err=%v", ok, err) } } func TestHashesAreSalted(t *testing.T) { a, _ := HashPassword("same") b, _ := HashPassword("same") if a == b { t.Fatal("two hashes of the same password must differ") } } func TestVerifyRejectsGarbage(t *testing.T) { for _, bad := range []string{ "", "plaintext", "$bcrypt$whatever", "$argon2id$v=19$m=65536,t=3,p=2$!!!$AAA", "$argon2id$v=18$m=65536,t=3,p=2$c2FsdA$AAA", } { if ok, err := VerifyPassword("x", bad); ok || err == nil { t.Errorf("VerifyPassword(%q): ok=%v err=%v, want rejection", bad, ok, err) } } } func TestVerifyHonorsEmbeddedParams(t *testing.T) { // A hash created with different (weaker) params must still verify: // params come from the hash string, not the current constants. salt := []byte("saltsaltsaltsalt") key := argon2.IDKey([]byte("pw"), salt, 1, 16*1024, 1, 32) legacy := fmt.Sprintf("$argon2id$v=19$m=16384,t=1,p=1$%s$%s", base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(key)) ok, err := VerifyPassword("pw", legacy) if err != nil || !ok { t.Fatalf("legacy-param hash: ok=%v err=%v", ok, err) } } func TestNewTokenShape(t *testing.T) { a, b := NewToken(), NewToken() if a == b { t.Fatal("tokens must be unique") } if len(a) != 43 { // 32 bytes base64url, unpadded t.Fatalf("token length = %d, want 43", len(a)) } }