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:
StarFleetCPTN
2025-03-16 16:12:42 -07:00
parent 522699d96a
commit b1db53b89e
32 changed files with 676 additions and 548 deletions
+15 -6
View File
@@ -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
+3 -3
View File
@@ -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
}
+14 -14
View File
@@ -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)
+76
View File
@@ -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": "",
})
}
+3
View File
@@ -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)