mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
feat: Refactor Google Drive and Google Photos authentication handling
- Separate built-in authentication options for source and destination in configuration forms. - Update UI components to reflect changes in authentication handling for Google Drive and Google Photos. - Implement path validation for local directories in the configuration forms, enhancing user experience. - Revise README to clarify supported source and destination types, including updated terminology for Google Drive. - Introduce new API endpoint for path validation, improving error handling and user feedback.
This commit is contained in:
@@ -71,6 +71,7 @@ func (h *Handlers) HandleEditConfig(c *gin.Context) {
|
||||
// HandleCreateConfig handles the POST /configs route
|
||||
func (h *Handlers) HandleCreateConfig(c *gin.Context) {
|
||||
var config db.TransferConfig
|
||||
|
||||
if err := c.ShouldBind(&config); err != nil {
|
||||
log.Printf("Error binding config form: %v", err)
|
||||
c.String(http.StatusBadRequest, fmt.Sprintf("Invalid form data: %v", err))
|
||||
@@ -118,9 +119,13 @@ func (h *Handlers) HandleCreateConfig(c *gin.Context) {
|
||||
sourceIncludeArchivedValue := sourceIncludeArchivedVal == "on" || sourceIncludeArchivedVal == "true"
|
||||
config.SourceIncludeArchived = &sourceIncludeArchivedValue
|
||||
|
||||
useBuiltinAuthVal := c.Request.FormValue("use_builtin_auth")
|
||||
useBuiltinAuthValue := useBuiltinAuthVal == "on" || useBuiltinAuthVal == "true"
|
||||
config.UseBuiltinAuth = &useBuiltinAuthValue
|
||||
useBuiltinAuthSourceVal := c.Request.FormValue("use_builtin_auth_source")
|
||||
useBuiltinAuthSourceValue := useBuiltinAuthSourceVal == "on" || useBuiltinAuthSourceVal == "true"
|
||||
config.UseBuiltinAuthSource = &useBuiltinAuthSourceValue
|
||||
|
||||
useBuiltinAuthDestVal := c.Request.FormValue("use_builtin_auth_dest")
|
||||
useBuiltinAuthDestValue := useBuiltinAuthDestVal == "on" || useBuiltinAuthDestVal == "true"
|
||||
config.UseBuiltinAuthDest = &useBuiltinAuthDestValue
|
||||
|
||||
if err := h.DB.Create(&config).Error; err != nil {
|
||||
log.Printf("Error creating config: %v", err)
|
||||
@@ -209,9 +214,13 @@ func (h *Handlers) HandleUpdateConfig(c *gin.Context) {
|
||||
sourceIncludeArchivedValue := sourceIncludeArchivedVal == "on" || sourceIncludeArchivedVal == "true"
|
||||
config.SourceIncludeArchived = &sourceIncludeArchivedValue
|
||||
|
||||
useBuiltinAuthVal := c.Request.FormValue("use_builtin_auth")
|
||||
useBuiltinAuthValue := useBuiltinAuthVal == "on" || useBuiltinAuthVal == "true"
|
||||
config.UseBuiltinAuth = &useBuiltinAuthValue
|
||||
useBuiltinAuthSourceVal := c.Request.FormValue("use_builtin_auth_source")
|
||||
useBuiltinAuthSourceValue := useBuiltinAuthSourceVal == "on" || useBuiltinAuthSourceVal == "true"
|
||||
config.UseBuiltinAuthSource = &useBuiltinAuthSourceValue
|
||||
|
||||
useBuiltinAuthDestVal := c.Request.FormValue("use_builtin_auth_dest")
|
||||
useBuiltinAuthDestValue := useBuiltinAuthDestVal == "on" || useBuiltinAuthDestVal == "true"
|
||||
config.UseBuiltinAuthDest = &useBuiltinAuthDestValue
|
||||
|
||||
// Preserve fields that shouldn't be updated
|
||||
config.CreatedBy = oldConfig.CreatedBy
|
||||
|
||||
@@ -37,7 +37,7 @@ func (h *Handlers) HandleGDriveAuth(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Ensure it's a Google Drive or Google Photos configuration
|
||||
if config.DestinationType != "gdrive" && config.DestinationType != "gphotos" {
|
||||
if config.SourceType != "gdrive" && config.DestinationType != "gdrive" && config.SourceType != "gphotos" && config.DestinationType != "gphotos" {
|
||||
RenderErrorPage(c, "Not a Google configuration", "The selected configuration is not set up for Google Drive or Google Photos")
|
||||
return
|
||||
}
|
||||
@@ -365,8 +365,8 @@ func (h *Handlers) HandleGDriveTokenProcess(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure it's a Google Drive configuration
|
||||
if config.DestinationType != "gdrive" {
|
||||
// Ensure it's a Google Drive or Google Photos configuration
|
||||
if config.SourceType != "gdrive" && config.DestinationType != "gdrive" && config.SourceType != "gphotos" && config.DestinationType != "gphotos" {
|
||||
RenderErrorPage(c, "Not a Google Drive configuration", "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,26 +64,26 @@ func (h *MockHandlers) HandleGDriveAuth(c *gin.Context) {
|
||||
// Get the config ID from the query parameter
|
||||
configIDStr := c.Param("id")
|
||||
if configIDStr == "" {
|
||||
RenderErrorPage(c, "Missing configuration ID", "")
|
||||
RenderErrorPageTest(c, "Missing configuration ID", "")
|
||||
return
|
||||
}
|
||||
|
||||
configID, err := strconv.ParseUint(configIDStr, 10, 64)
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Invalid configuration ID", err.Error())
|
||||
RenderErrorPageTest(c, "Invalid configuration ID", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get the configuration
|
||||
config, err := h.DB.GetTransferConfig(uint(configID))
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Configuration not found", err.Error())
|
||||
RenderErrorPageTest(c, "Configuration not found", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure it's a Google Drive or Google Photos configuration
|
||||
if config.DestinationType != "gdrive" && config.DestinationType != "gphotos" {
|
||||
RenderErrorPage(c, "Not a Google configuration", "The selected configuration is not set up for Google Drive or Google Photos")
|
||||
RenderErrorPageTest(c, "Not a Google configuration", "The selected configuration is not set up for Google Drive or Google Photos")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,14 +96,14 @@ func (h *MockHandlers) HandleGDriveAuth(c *gin.Context) {
|
||||
// Get Rclone Config Path
|
||||
rcloneConfigPath := h.DB.GetConfigRclonePath(config)
|
||||
if rcloneConfigPath == "" {
|
||||
RenderErrorPage(c, "Rclone config not found", "The selected configuration does not have a valid rclone config")
|
||||
RenderErrorPageTest(c, "Rclone config not found", "The selected configuration does not have a valid rclone config")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary config file for authentication
|
||||
tempConfigDir := filepath.Join(dataDir, "temp")
|
||||
if err := os.MkdirAll(tempConfigDir, 0755); err != nil {
|
||||
RenderErrorPage(c, "Failed to create temporary directory", err.Error())
|
||||
RenderErrorPageTest(c, "Failed to create temporary directory", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func (h *MockHandlers) HandleGDriveAuthCallback(c *gin.Context) {
|
||||
// Get auth code from query parameters
|
||||
authCode := c.Query("code")
|
||||
if authCode == "" {
|
||||
RenderErrorPage(c, "Authentication failed", "No authorization code received from Google")
|
||||
RenderErrorPageTest(c, "Authentication failed", "No authorization code received from Google")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,27 +188,27 @@ func (h *MockHandlers) HandleGDriveAuthCallback(c *gin.Context) {
|
||||
state := c.Query("state")
|
||||
storedState, err := c.Cookie("gdrive_auth_state")
|
||||
if err != nil || state != storedState {
|
||||
RenderErrorPage(c, "Authentication failed", "Invalid state parameter")
|
||||
RenderErrorPageTest(c, "Authentication failed", "Invalid state parameter")
|
||||
return
|
||||
}
|
||||
|
||||
// Get config ID from cookie
|
||||
configIDStr, err := c.Cookie("gdrive_config_id")
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Authentication failed", "Unable to retrieve configuration ID")
|
||||
RenderErrorPageTest(c, "Authentication failed", "Unable to retrieve configuration ID")
|
||||
return
|
||||
}
|
||||
|
||||
configID, err := strconv.ParseUint(configIDStr, 10, 64)
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Invalid configuration ID", err.Error())
|
||||
RenderErrorPageTest(c, "Invalid configuration ID", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get the configuration
|
||||
config, err := h.DB.GetTransferConfig(uint(configID))
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Failed to get configuration", err.Error())
|
||||
RenderErrorPageTest(c, "Failed to get configuration", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ func (h *MockHandlers) HandleGDriveAuthCallback(c *gin.Context) {
|
||||
// Update the config with the token
|
||||
err = h.DB.GenerateRcloneConfigWithToken(config, mockToken)
|
||||
if err != nil {
|
||||
RenderErrorPage(c, "Failed to update configuration", err.Error())
|
||||
RenderErrorPageTest(c, "Failed to update configuration", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -420,8 +420,8 @@ func TestHandleGDriveAuth_NonGoogleConfig(t *testing.T) {
|
||||
assert.Contains(t, w.Body.String(), "Not a Google configuration")
|
||||
}
|
||||
|
||||
// RenderErrorPage renders an error page with the given message
|
||||
func RenderErrorPage(c *gin.Context, title string, details string) {
|
||||
// RenderErrorPageTest renders an error page with the given message
|
||||
func RenderErrorPageTest(c *gin.Context, title string, details string) {
|
||||
// Here we'd typically use a component for error display
|
||||
// For now, we'll just render a simple HTML error page for testing
|
||||
errorHTML := fmt.Sprintf("<html><body><h1>Error: %s</h1><p>%s</p></body></html>", title, details)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HandleCheckPath validates if a given path exists and is accessible
|
||||
func (h *Handlers) HandleCheckPath(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
if path == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"valid": false,
|
||||
"error": "No path provided",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Clean and resolve the path
|
||||
path = filepath.Clean(path)
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": false,
|
||||
"error": "Invalid path format",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if path exists
|
||||
info, err := os.Stat(absPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": false,
|
||||
"error": "Path does not exist",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": false,
|
||||
"error": "Error accessing path: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's a directory
|
||||
if !info.IsDir() {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": false,
|
||||
"error": "Path exists but is not a directory",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we have read access
|
||||
testFile := filepath.Join(absPath, ".gomft_test")
|
||||
f, err := os.OpenFile(testFile, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": false,
|
||||
"error": "Directory exists but is not writable",
|
||||
})
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
os.Remove(testFile)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": true,
|
||||
"error": "",
|
||||
})
|
||||
}
|
||||
@@ -32,6 +32,9 @@ func (h *Handlers) RegisterRoutes(router *gin.Engine) {
|
||||
authorized.POST("/configs/:id", h.HandleUpdateConfig)
|
||||
authorized.DELETE("/configs/:id", h.HandleDeleteConfig)
|
||||
|
||||
// Path validation endpoint
|
||||
authorized.GET("/check-path", h.HandleCheckPath)
|
||||
|
||||
// Google Drive authentication routes
|
||||
authorized.GET("/configs/:id/gdrive-auth", h.HandleGDriveAuth)
|
||||
authorized.GET("/configs/gdrive-callback", h.HandleGDriveAuthCallback)
|
||||
|
||||
Reference in New Issue
Block a user