package auth import ( "encoding/base64" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "pipu/api/internal/platform/apperr" ) var fastArgon = Argon2Params{MemoryKiB: 64, Time: 1, Threads: 1, SaltLen: 16, KeyLen: 32} func TestHasher(t *testing.T) { h := NewHasher(fastArgon) enc, err := h.Hash("correct horse") require.NoError(t, err) assert.True(t, strings.HasPrefix(enc, "$argon2id$v=19$m=64,t=1,p=1$"), enc) ok, rehash, err := h.Verify("correct horse", enc) require.NoError(t, err) assert.True(t, ok) assert.False(t, rehash) ok, _, err = h.Verify("wrong horse", enc) require.NoError(t, err) assert.False(t, ok) other, _ := h.Hash("correct horse") assert.NotEqual(t, enc, other, "salt is random") stronger := NewHasher(Argon2Params{MemoryKiB: 128, Time: 1, Threads: 1, SaltLen: 16, KeyLen: 32}) ok, rehash, err = stronger.Verify("correct horse", enc) require.NoError(t, err) assert.True(t, ok, "old params are read from the hash") assert.True(t, rehash) } func TestHasherRejectsMalformed(t *testing.T) { h := NewHasher(fastArgon) for _, s := range []string{"", "plain", "$argon2i$v=19$m=64,t=1,p=1$c2FsdA$a2V5", "$argon2id$v=18$m=64,t=1,p=1$c2FsdA$a2V5", "$argon2id$v=19$m=x,t=1,p=1$c2FsdA$a2V5", "$argon2id$v=19$m=64,t=1,p=1$!!$a2V5", "$argon2id$v=19$m=64,t=1,p=1$c2FsdA$"} { _, _, err := h.Verify("x", s) assert.Error(t, err, s) } } func TestGeneratePassword(t *testing.T) { seen := map[string]bool{} for range 50 { p, err := GeneratePassword(12) require.NoError(t, err) assert.Len(t, p, 12) for _, r := range p { assert.Contains(t, tempPasswordAlphabet, string(r)) } seen[p] = true } assert.Len(t, seen, 50) } func TestValidatePassword(t *testing.T) { cases := []struct { name, password, username string role Role code string }{ {"user ok", "eightchr", "alice", RoleUser, ""}, {"user short", "sevench", "alice", RoleUser, CodePasswordTooShort}, {"moderator needs 12", "elevenchars", "mod", RoleModerator, CodePasswordTooShort}, {"admin ok", "twelve-chars", "root", RoleAdmin, ""}, {"runes not bytes", "пароль-ок", "alice", RoleUser, ""}, {"same as username", "Alexander", "alexander", RoleUser, CodePasswordSameAsUsername}, {"common", "Password123", "alice", RoleUser, CodePasswordTooCommon}, {"common cyrillic", "пароль123", "alice", RoleUser, CodePasswordTooCommon}, {"too long", strings.Repeat("a", MaxPassword+1), "alice", RoleUser, CodePasswordTooLong}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := ValidatePassword("new_password", tc.password, tc.username, tc.role) if tc.code == "" { assert.NoError(t, err) return } var ae *apperr.Error require.ErrorAs(t, err, &ae) assert.Equal(t, tc.code, ae.Code) assert.Equal(t, "new_password", ae.Fields[0].Field) }) } } func TestValidateUsername(t *testing.T) { for _, ok := range []string{"abc", "john.doe", "a_b-c", strings.Repeat("a", 32)} { assert.NoError(t, ValidateUsername("username", ok), ok) } for _, bad := range []string{"ab", "John", "имя", "a b", "a@b", strings.Repeat("a", 33)} { assert.Error(t, ValidateUsername("username", bad), bad) } assert.Equal(t, "john", NormalizeUsername(" John ")) } func TestParsePrivateKey(t *testing.T) { seed := "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=" k1, err := ParsePrivateKey(seed) require.NoError(t, err) k2, err := ParsePrivateKey(strings.TrimRight(strings.NewReplacer("+", "-", "/", "_").Replace(seed), "=")) require.NoError(t, err) assert.Equal(t, k1, k2) k3, err := ParsePrivateKey(b64std(k1)) require.NoError(t, err) assert.Equal(t, k1, k3, "64-byte key form") _, err = ParsePrivateKey("c2hvcnQ=") assert.Error(t, err) _, err = ParsePrivateKey("not base64 !!") assert.Error(t, err) } func b64std(b []byte) string { return base64.StdEncoding.EncodeToString(b) }