feat: add password management, reading persistence on feed switch, and spin animation
This commit is contained in:
@@ -84,6 +84,29 @@ func (s *AuthService) Authenticate(username, password string) (string, error) {
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) UpdatePassword(userID, oldPassword, newPassword string) error {
|
||||
var currentHash string
|
||||
err := s.db.QueryRow("SELECT password_hash FROM users WHERE id = ?", userID).Scan(¤tHash)
|
||||
if err != nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(currentHash), []byte(oldPassword)); err != nil {
|
||||
return errors.New("incorrect current password")
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(newPassword)) < 6 {
|
||||
return errors.New("new password must be at least 6 characters long")
|
||||
}
|
||||
|
||||
newHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash new password: %w", err)
|
||||
}
|
||||
|
||||
return s.db.UpdateUserPassword(userID, string(newHash))
|
||||
}
|
||||
|
||||
func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.pengzhan.dev/haopengzhan/rsspipe/internal/config"
|
||||
"git.pengzhan.dev/haopengzhan/rsspipe/internal/db"
|
||||
)
|
||||
|
||||
func TestAuthService(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "rsspipe-auth-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
dbPath := filepath.Join(tempDir, "auth_test.db")
|
||||
database, err := db.InitDB(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("InitDB failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = database.Close()
|
||||
}()
|
||||
|
||||
cfg := &config.Config{
|
||||
AdminUsername: "admin",
|
||||
AdminPassword: "initial_password_123",
|
||||
JWTSecret: "super-secret-jwt-key-for-test",
|
||||
}
|
||||
|
||||
authService := NewAuthService(database, cfg)
|
||||
|
||||
// 1. Bootstrap Admin
|
||||
if err := authService.BootstrapAdmin(); err != nil {
|
||||
t.Fatalf("BootstrapAdmin failed: %v", err)
|
||||
}
|
||||
|
||||
// 2. Authenticate with initial password
|
||||
token, err := authService.Authenticate("admin", "initial_password_123")
|
||||
if err != nil {
|
||||
t.Fatalf("Authenticate failed: %v", err)
|
||||
}
|
||||
if token == "" {
|
||||
t.Fatalf("expected non-empty token")
|
||||
}
|
||||
|
||||
// 3. Validate Token
|
||||
claims, err := authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateToken failed: %v", err)
|
||||
}
|
||||
if claims.Username != "admin" {
|
||||
t.Errorf("expected username admin, got %s", claims.Username)
|
||||
}
|
||||
|
||||
// 4. Update Password
|
||||
if err := authService.UpdatePassword(claims.UserID, "wrong_old_pwd", "new_secure_pwd_456"); err == nil {
|
||||
t.Errorf("expected error with incorrect old password")
|
||||
}
|
||||
|
||||
if err := authService.UpdatePassword(claims.UserID, "initial_password_123", "short"); err == nil {
|
||||
t.Errorf("expected error with too short new password")
|
||||
}
|
||||
|
||||
if err := authService.UpdatePassword(claims.UserID, "initial_password_123", "new_secure_pwd_456"); err != nil {
|
||||
t.Fatalf("UpdatePassword failed: %v", err)
|
||||
}
|
||||
|
||||
// 5. Authenticate with new password
|
||||
newToken, err := authService.Authenticate("admin", "new_secure_pwd_456")
|
||||
if err != nil {
|
||||
t.Fatalf("Authenticate with new password failed: %v", err)
|
||||
}
|
||||
if newToken == "" {
|
||||
t.Fatalf("expected non-empty token for new password")
|
||||
}
|
||||
|
||||
// 6. Old password should now fail
|
||||
if _, err := authService.Authenticate("admin", "initial_password_123"); err == nil {
|
||||
t.Errorf("expected failure when authenticating with old password")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user