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.
This commit is contained in:
parent
2aea1af361
commit
7fa314866e
3 changed files with 6 additions and 210 deletions
1
.github/workflows/backend-ci.yml
vendored
1
.github/workflows/backend-ci.yml
vendored
|
|
@ -47,6 +47,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: ~/go/bin/govulncheck
|
path: ~/go/bin/govulncheck
|
||||||
key: ${{ runner.os }}-govulncheck-latest
|
key: ${{ runner.os }}-govulncheck-latest
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -38,6 +38,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: ~/go/bin
|
path: ~/go/bin
|
||||||
key: ${{ runner.os }}-go-tools-govulncheck-golangci-lint-v2
|
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
|
- name: Install Go tools
|
||||||
# NOTE: golangci-lint v2 lives under the /v2/ module path.
|
# NOTE: golangci-lint v2 lives under the /v2/ module path.
|
||||||
|
|
@ -141,6 +144,7 @@ jobs:
|
||||||
~/.rustup
|
~/.rustup
|
||||||
~/.cargo/bin
|
~/.cargo/bin
|
||||||
key: ${{ runner.os }}-rustup-stable-rustfmt-clippy-audit-tarpaulin
|
key: ${{ runner.os }}-rustup-stable-rustfmt-clippy-audit-tarpaulin
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
if: steps.rustup-cache.outputs.cache-hit != 'true'
|
if: steps.rustup-cache.outputs.cache-hit != 'true'
|
||||||
|
|
@ -160,6 +164,7 @@ jobs:
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cargo-
|
${{ runner.os }}-cargo-
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build
|
run: cargo build
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue