axe/internal/auth/jwt_test.go
2026-10-18 22:07:25 +03:00

105 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package auth
import (
"encoding/base64"
"strings"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
)
const testSecret = "test-secret-at-least-32-characters-long"
func TestGenerateAndParseToken(t *testing.T) {
token, err := GenerateToken(testSecret, "user-1", "worker")
if err != nil {
t.Fatalf("generate: %v", err)
}
claims, err := ParseToken(testSecret, token)
if err != nil {
t.Fatalf("parse: %v", err)
}
if claims.UserID != "user-1" || claims.Role != "worker" {
t.Errorf("claims = %+v, want user_id=user-1 role=worker", claims)
}
}
func TestParseTokenRejects(t *testing.T) {
cases := []struct {
name string
token string
}{
{
name: "подписан другим секретом",
token: sign(t, "another-secret", jwt.SigningMethodHS256, time.Hour),
},
{
name: "другой алгоритм при том же секрете (HS512)",
token: sign(t, testSecret, jwt.SigningMethodHS512, time.Hour),
},
{
name: "просроченный",
token: sign(t, testSecret, jwt.SigningMethodHS256, -time.Hour),
},
{
name: "alg=none, подпись не подставлена",
token: forgeNone(t),
},
{
name: "мусор вместо токена",
token: "not-a-token",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if _, err := ParseToken(testSecret, tc.token); err == nil {
t.Error("token was accepted, want rejection")
}
})
}
}
func TestEmptySecretFailsFast(t *testing.T) {
if _, err := GenerateToken("", "user-1", "client"); err == nil {
t.Error("GenerateToken with empty secret: want error, got token")
}
if _, err := ParseToken("", "whatever"); err == nil {
t.Error("ParseToken with empty secret: want error, got claims")
}
}
// sign собирает токен вручную, чтобы протестировать сценарии,
// которые GenerateToken сам не порождает.
func sign(t *testing.T, secret string, method jwt.SigningMethod, ttl time.Duration) string {
t.Helper()
claims := Claims{
UserID: "user-1",
Role: "admin",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token, err := jwt.NewWithClaims(method, claims).SignedString([]byte(secret))
if err != nil {
t.Fatalf("sign: %v", err)
}
return token
}
// forgeNone подделывает токен с alg=none: раньше такой заголовок
// проходил, потому что ключевая функция не сверяла метод подписи.
func forgeNone(t *testing.T) string {
t.Helper()
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`))
payload := base64.RawURLEncoding.EncodeToString(
[]byte(`{"user_id":"hacker","role":"admin","exp":4102444800}`),
)
return strings.Join([]string{header, payload, ""}, ".")
}