From 7fa314866eff935f9560f7a770c5aa60a0251183 Mon Sep 17 00:00:00 2001 From: senke Date: Tue, 14 Apr 2026 18:01:40 +0200 Subject: [PATCH] ci(cache): add save-always to persist cache on job failure By default actions/cache@v4 only saves the cache when the job completes successfully. Runs 71 / 74 failed at the Lint / Install Go tools step before reaching the post-step cache upload, so the Go tool binaries cache (govulncheck + golangci-lint) was never persisted and every subsequent run paid the ~3 min "go install @latest" cost again. Add `save-always: true` to: - Cache Go tool binaries (ci.yml) - Cache rustup toolchain (ci.yml) - Cache Cargo deps and target (ci.yml) - Cache govulncheck binary (backend-ci.yml) so the next run benefits from whatever the previous job managed to install, even if a downstream step later fails. --- .github/workflows/backend-ci.yml | 1 + .github/workflows/ci.yml | 5 + .../api/handlers/two_factor_handlers.go | 210 ------------------ 3 files changed, 6 insertions(+), 210 deletions(-) delete mode 100644 veza-backend-api/internal/api/handlers/two_factor_handlers.go diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index bb0e9cebb..64e0eabc1 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -47,6 +47,7 @@ jobs: with: path: ~/go/bin/govulncheck key: ${{ runner.os }}-govulncheck-latest + save-always: true - name: Run govulncheck run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ecec8279..89b3c0a7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,9 @@ jobs: with: path: ~/go/bin key: ${{ runner.os }}-go-tools-govulncheck-golangci-lint-v2 + # Save the cache even when later steps (Lint, Test, etc.) + # fail so the next run benefits from the installed tools. + save-always: true - name: Install Go tools # NOTE: golangci-lint v2 lives under the /v2/ module path. @@ -141,6 +144,7 @@ jobs: ~/.rustup ~/.cargo/bin key: ${{ runner.os }}-rustup-stable-rustfmt-clippy-audit-tarpaulin + save-always: true - name: Set up Rust if: steps.rustup-cache.outputs.cache-hit != 'true' @@ -160,6 +164,7 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- + save-always: true - name: Build run: cargo build diff --git a/veza-backend-api/internal/api/handlers/two_factor_handlers.go b/veza-backend-api/internal/api/handlers/two_factor_handlers.go deleted file mode 100644 index 455d74986..000000000 --- a/veza-backend-api/internal/api/handlers/two_factor_handlers.go +++ /dev/null @@ -1,210 +0,0 @@ -//go:build ignore -// +build ignore - -// DEPRECATED: Ce fichier n'est pas utilisé. Les routes 2FA utilisent internal/handlers/two_factor_handler.go -// qui exige le mot de passe pour DisableTwoFactor (SEC-001). Ne pas réactiver sans alignement. - -package handlers - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "go.uber.org/zap" - - "veza-backend-api/internal/services" -) - -// TwoFactorHandlers handles 2FA-related API endpoints -type TwoFactorHandlers struct { - twoFactorService *services.TwoFactorService - authService *services.AuthService - logger *zap.Logger -} - -// NewTwoFactorHandlers creates new 2FA handlers -func NewTwoFactorHandlers(twoFactorService *services.TwoFactorService, authService *services.AuthService, logger *zap.Logger) *TwoFactorHandlers { - return &TwoFactorHandlers{ - twoFactorService: twoFactorService, - authService: authService, - logger: logger, - } -} - -// InitTwoFactorHandlers initializes 2FA handlers -func InitTwoFactorHandlers(twoFactorService *services.TwoFactorService, authService *services.AuthService, logger *zap.Logger) { - handlers := NewTwoFactorHandlers(twoFactorService, authService, logger) - - // Store handlers globally for route registration - TwoFactorHandlersInstance = handlers -} - -// TwoFactorHandlersInstance holds the global 2FA handlers instance -var TwoFactorHandlersInstance *TwoFactorHandlers - -// SetupTwoFactor initiates 2FA setup for a user -func (h *TwoFactorHandlers) SetupTwoFactor(c *gin.Context) { - userID := c.GetInt64("user_id") - - // Get user information - user, err := h.authService.GetUserByID(c.Request.Context(), userID) - if err != nil { - h.logger.Error("Failed to get user", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user information"}) - return - } - - // Check if 2FA is already enabled - enabled, err := h.twoFactorService.GetTwoFactorStatus(c.Request.Context(), userID) - if err != nil { - h.logger.Error("Failed to get 2FA status", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get 2FA status"}) - return - } - - if enabled { - c.JSON(http.StatusBadRequest, gin.H{"error": "2FA is already enabled"}) - return - } - - // Generate 2FA setup - setup, err := h.twoFactorService.GenerateSecret(user) - if err != nil { - h.logger.Error("Failed to generate 2FA setup", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate 2FA setup"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "setup": setup, - }) -} - -// EnableTwoFactor enables 2FA for a user -func (h *TwoFactorHandlers) EnableTwoFactor(c *gin.Context) { - userID := c.GetInt64("user_id") - - var req struct { - Secret string `json:"secret" binding:"required"` - Code string `json:"code" binding:"required"` - RecoveryCodes []string `json:"recovery_codes" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Verify the code first - valid, err := h.twoFactorService.VerifyTwoFactor(c.Request.Context(), userID, req.Code) - if err != nil { - h.logger.Error("Failed to verify 2FA code", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify 2FA code"}) - return - } - - if !valid { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 2FA code"}) - return - } - - // Enable 2FA - err = h.twoFactorService.EnableTwoFactor(c.Request.Context(), userID, req.Secret, req.RecoveryCodes) - if err != nil { - h.logger.Error("Failed to enable 2FA", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to enable 2FA"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "2FA enabled successfully", - }) -} - -// DisableTwoFactor disables 2FA for a user -func (h *TwoFactorHandlers) DisableTwoFactor(c *gin.Context) { - userID := c.GetInt64("user_id") - - var req struct { - Code string `json:"code" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Verify the code first - valid, err := h.twoFactorService.VerifyTwoFactor(c.Request.Context(), userID, req.Code) - if err != nil { - h.logger.Error("Failed to verify 2FA code", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify 2FA code"}) - return - } - - if !valid { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 2FA code"}) - return - } - - // Disable 2FA - err = h.twoFactorService.DisableTwoFactor(c.Request.Context(), userID) - if err != nil { - h.logger.Error("Failed to disable 2FA", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to disable 2FA"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "2FA disabled successfully", - }) -} - -// VerifyTwoFactor verifies a 2FA code -func (h *TwoFactorHandlers) VerifyTwoFactor(c *gin.Context) { - userID := c.GetInt64("user_id") - - var req services.TwoFactorVerification - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Verify the code - valid, err := h.twoFactorService.VerifyTwoFactor(c.Request.Context(), userID, req.Code) - if err != nil { - h.logger.Error("Failed to verify 2FA code", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify 2FA code"}) - return - } - - if !valid { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 2FA code"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "2FA code verified successfully", - }) -} - -// GetTwoFactorStatus gets the 2FA status for a user -func (h *TwoFactorHandlers) GetTwoFactorStatus(c *gin.Context) { - userID := c.GetInt64("user_id") - - enabled, err := h.twoFactorService.GetTwoFactorStatus(c.Request.Context(), userID) - if err != nil { - h.logger.Error("Failed to get 2FA status", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get 2FA status"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "enabled": enabled, - }) -}