127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
|
|
package auth
|
||
|
|
|
||
|
|
import (
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestHashPassword_UsesArgon2id(t *testing.T) {
|
||
|
|
hashed, err := HashPassword("StrongPass1!")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("hash password failed: %v", err)
|
||
|
|
}
|
||
|
|
if !strings.HasPrefix(hashed, "$argon2id$") {
|
||
|
|
t.Fatalf("expected argon2id hash, got %q", hashed)
|
||
|
|
}
|
||
|
|
if !VerifyPassword(hashed, "StrongPass1!") {
|
||
|
|
t.Fatal("expected argon2id password verification to succeed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestVerifyPassword_SupportsLegacyBcrypt(t *testing.T) {
|
||
|
|
hashed, err := BcryptHash("LegacyPass1!")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("hash legacy bcrypt password failed: %v", err)
|
||
|
|
}
|
||
|
|
if !VerifyPassword(hashed, "LegacyPass1!") {
|
||
|
|
t.Fatal("expected bcrypt compatibility verification to succeed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNewJWTWithOptions_RS256(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
jwtManager, err := NewJWTWithOptions(JWTOptions{
|
||
|
|
Algorithm: jwtAlgorithmRS256,
|
||
|
|
RSAPrivateKeyPath: filepath.Join(dir, "private.pem"),
|
||
|
|
RSAPublicKeyPath: filepath.Join(dir, "public.pem"),
|
||
|
|
AccessTokenExpire: 2 * time.Hour,
|
||
|
|
RefreshTokenExpire: 24 * time.Hour,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create rs256 jwt manager failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
accessToken, refreshToken, err := jwtManager.GenerateTokenPair(42, "rs256-user")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("generate token pair failed: %v", err)
|
||
|
|
}
|
||
|
|
if jwtManager.GetAlgorithm() != jwtAlgorithmRS256 {
|
||
|
|
t.Fatalf("unexpected algorithm: %s", jwtManager.GetAlgorithm())
|
||
|
|
}
|
||
|
|
|
||
|
|
accessClaims, err := jwtManager.ValidateAccessToken(accessToken)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("validate access token failed: %v", err)
|
||
|
|
}
|
||
|
|
if accessClaims.UserID != 42 || accessClaims.Username != "rs256-user" {
|
||
|
|
t.Fatalf("unexpected access claims: %+v", accessClaims)
|
||
|
|
}
|
||
|
|
|
||
|
|
refreshClaims, err := jwtManager.ValidateRefreshToken(refreshToken)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("validate refresh token failed: %v", err)
|
||
|
|
}
|
||
|
|
if refreshClaims.Type != "refresh" {
|
||
|
|
t.Fatalf("unexpected refresh claims: %+v", refreshClaims)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNewJWTWithOptions_RS256_RequiresKeyMaterial(t *testing.T) {
|
||
|
|
_, err := NewJWTWithOptions(JWTOptions{
|
||
|
|
Algorithm: jwtAlgorithmRS256,
|
||
|
|
AccessTokenExpire: 2 * time.Hour,
|
||
|
|
RefreshTokenExpire: 24 * time.Hour,
|
||
|
|
})
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected RS256 without key material to fail")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNewJWTWithOptions_RS256_RequireExistingKeysRejectsMissingFiles(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
_, err := NewJWTWithOptions(JWTOptions{
|
||
|
|
Algorithm: jwtAlgorithmRS256,
|
||
|
|
RSAPrivateKeyPath: filepath.Join(dir, "missing-private.pem"),
|
||
|
|
RSAPublicKeyPath: filepath.Join(dir, "missing-public.pem"),
|
||
|
|
RequireExistingRSAKeys: true,
|
||
|
|
AccessTokenExpire: 2 * time.Hour,
|
||
|
|
RefreshTokenExpire: 24 * time.Hour,
|
||
|
|
})
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected RS256 strict mode to reject missing key files")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNewJWTWithOptions_RS256_RequireExistingKeysAllowsExistingFiles(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
privatePath := filepath.Join(dir, "private.pem")
|
||
|
|
publicPath := filepath.Join(dir, "public.pem")
|
||
|
|
|
||
|
|
if _, err := NewJWTWithOptions(JWTOptions{
|
||
|
|
Algorithm: jwtAlgorithmRS256,
|
||
|
|
RSAPrivateKeyPath: privatePath,
|
||
|
|
RSAPublicKeyPath: publicPath,
|
||
|
|
AccessTokenExpire: 2 * time.Hour,
|
||
|
|
RefreshTokenExpire: 24 * time.Hour,
|
||
|
|
}); err != nil {
|
||
|
|
t.Fatalf("prepare key files failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
jwtManager, err := NewJWTWithOptions(JWTOptions{
|
||
|
|
Algorithm: jwtAlgorithmRS256,
|
||
|
|
RSAPrivateKeyPath: privatePath,
|
||
|
|
RSAPublicKeyPath: publicPath,
|
||
|
|
RequireExistingRSAKeys: true,
|
||
|
|
AccessTokenExpire: 2 * time.Hour,
|
||
|
|
RefreshTokenExpire: 24 * time.Hour,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("expected strict mode to accept existing key files, got: %v", err)
|
||
|
|
}
|
||
|
|
if jwtManager.GetAlgorithm() != jwtAlgorithmRS256 {
|
||
|
|
t.Fatalf("unexpected algorithm: %s", jwtManager.GetAlgorithm())
|
||
|
|
}
|
||
|
|
}
|