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()) } }