mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
- Add Google Drive as a source and destination option in the configuration forms. - Implement Google Drive authentication flow and token management. - Update job and configuration handlers to support Google Drive-specific settings. - Enhance UI components to include Google Drive configuration templates. - Introduce new tests for Google Drive integration and ensure proper handling of authentication and configuration. - Update database migrations to accommodate new fields related to Google Drive configurations.
240 lines
6.3 KiB
Go
240 lines
6.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GoogleDriveAuthHandler initiates the Google Drive OAuth flow
|
|
func (h *Handlers) HandleGoogleDriveAuth(c *gin.Context) {
|
|
configID := c.Query("config_id")
|
|
if configID == "" {
|
|
RenderErrorPage(c, "Missing config_id parameter", "")
|
|
return
|
|
}
|
|
|
|
// Prepare for the OAuth flow
|
|
dataDir := os.Getenv("DATA_DIR")
|
|
if dataDir == "" {
|
|
dataDir = "./data"
|
|
}
|
|
|
|
// Ensure oauth directory exists
|
|
oauthDir := filepath.Join(dataDir, "oauth")
|
|
if err := os.MkdirAll(oauthDir, 0755); err != nil {
|
|
RenderErrorPage(c, "Failed to create oauth directory", err.Error())
|
|
return
|
|
}
|
|
|
|
// Get rclone path
|
|
rclonePath := os.Getenv("RCLONE_PATH")
|
|
if rclonePath == "" {
|
|
rclonePath = "rclone"
|
|
}
|
|
|
|
// Set up a temporary rclone config
|
|
tempConfigPath := filepath.Join(oauthDir, fmt.Sprintf("temp_gdrive_%s.conf", configID))
|
|
|
|
// Build rclone command to get auth URL
|
|
cmd := exec.Command(
|
|
rclonePath,
|
|
"config",
|
|
"create",
|
|
"temp_gdrive",
|
|
"drive",
|
|
"--config",
|
|
tempConfigPath,
|
|
)
|
|
|
|
// Set a timeout context
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Run the command with proper context handling
|
|
// We can't use exec.CommandContext directly since we're creating the command differently
|
|
// So we'll use a goroutine with the context's Done() channel to handle cancellation
|
|
go func() {
|
|
<-ctx.Done() // Wait for context to be done (timeout or cancellation)
|
|
if cmd.Process != nil {
|
|
if err := cmd.Process.Kill(); err != nil {
|
|
RenderErrorPage(c, "Failed to kill rclone process", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Run the command to get the browser URL (this will fail in a specific way)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
outputStr := string(output)
|
|
|
|
// Look for the URL in the output
|
|
authURL := extractAuthURL(outputStr)
|
|
if authURL == "" {
|
|
RenderErrorPage(c, "Failed to get Google Drive authentication URL", outputStr)
|
|
return
|
|
}
|
|
|
|
// Store the config ID in the session
|
|
session := sessions.Default(c)
|
|
session.Set("gdrive_config_id", configID)
|
|
session.Set("gdrive_temp_config", tempConfigPath)
|
|
if err := session.Save(); err != nil {
|
|
RenderErrorPage(c, "Failed to save session", err.Error())
|
|
return
|
|
}
|
|
|
|
// Use component rendering instead of HTML template
|
|
// This would typically use a component like:
|
|
// components.GDriveAuth(c.Request.Context(), components.GDriveAuthData{
|
|
// AuthURL: authURL,
|
|
// ConfigID: configID,
|
|
// }).Render(c.Request.Context(), c.Writer)
|
|
// For now, we'll redirect to the configs page with the auth URL and config ID
|
|
c.Redirect(http.StatusFound, fmt.Sprintf("/configs/%s/gdrive-auth?auth_url=%s",
|
|
configID, url.QueryEscape(authURL)))
|
|
return
|
|
}
|
|
|
|
// If we get here, something unexpected happened
|
|
RenderErrorPage(c, "Unexpected result from rclone", string(output))
|
|
}
|
|
|
|
// HandleGoogleDriveCallback handles the manual entry of the OAuth code
|
|
func (h *Handlers) HandleGoogleDriveCallback(c *gin.Context) {
|
|
// Get the auth code from form submission
|
|
authCode := c.PostForm("auth_code")
|
|
if authCode == "" {
|
|
RenderErrorPage(c, "Missing authentication code", "")
|
|
return
|
|
}
|
|
|
|
// Get the config ID from the session
|
|
session := sessions.Default(c)
|
|
configID := session.Get("gdrive_config_id")
|
|
tempConfigPath := session.Get("gdrive_temp_config")
|
|
|
|
if configID == nil || tempConfigPath == nil {
|
|
RenderErrorPage(c, "Session expired or invalid. Please try again.", "")
|
|
return
|
|
}
|
|
|
|
// Get rclone path
|
|
rclonePath := os.Getenv("RCLONE_PATH")
|
|
if rclonePath == "" {
|
|
rclonePath = "rclone"
|
|
}
|
|
|
|
// Complete the OAuth flow with the provided code
|
|
cmd := exec.Command(
|
|
rclonePath,
|
|
"config",
|
|
"reconnect",
|
|
"temp_gdrive:",
|
|
"--config",
|
|
tempConfigPath.(string),
|
|
)
|
|
|
|
// Create a pipe for stdin
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
RenderErrorPage(c, "Failed to create stdin pipe", err.Error())
|
|
return
|
|
}
|
|
|
|
// Start the command
|
|
if err := cmd.Start(); err != nil {
|
|
RenderErrorPage(c, "Failed to start rclone command", err.Error())
|
|
return
|
|
}
|
|
|
|
// Write the auth code to stdin
|
|
fmt.Fprintln(stdin, authCode)
|
|
stdin.Close()
|
|
|
|
// Wait for the command to complete
|
|
if err := cmd.Wait(); err != nil {
|
|
RenderErrorPage(c, "Failed to complete Google Drive authentication", err.Error())
|
|
return
|
|
}
|
|
|
|
// Read the token from the config file
|
|
configData, err := ioutil.ReadFile(tempConfigPath.(string))
|
|
if err != nil {
|
|
RenderErrorPage(c, "Failed to read token from config file", err.Error())
|
|
return
|
|
}
|
|
|
|
// Extract token from config
|
|
token := extractToken(string(configData))
|
|
if token == "" {
|
|
RenderErrorPage(c, "Failed to extract token from config", "")
|
|
return
|
|
}
|
|
|
|
// Store the token in the database
|
|
configIDStr := configID.(string)
|
|
if err := h.DB.StoreGoogleDriveToken(configIDStr, token); err != nil {
|
|
RenderErrorPage(c, "Failed to save token", err.Error())
|
|
return
|
|
}
|
|
|
|
// Clean up temporary config
|
|
os.Remove(tempConfigPath.(string))
|
|
|
|
// Clear session data
|
|
session.Delete("gdrive_config_id")
|
|
session.Delete("gdrive_temp_config")
|
|
if err := session.Save(); err != nil {
|
|
RenderErrorPage(c, "Failed to save session", err.Error())
|
|
return
|
|
}
|
|
|
|
// Redirect to the configs page
|
|
c.Redirect(http.StatusFound, "/configs?status=gdrive_auth_success")
|
|
}
|
|
|
|
// Helper function to extract the authentication URL from rclone output
|
|
func extractAuthURL(output string) string {
|
|
// This is a simplified version - you may need to improve the regex
|
|
// to handle different output formats from rclone
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "http") && strings.Contains(line, "accounts.google.com") {
|
|
// Extract the URL - this is a simplified approach
|
|
words := strings.Fields(line)
|
|
for _, word := range words {
|
|
if strings.HasPrefix(word, "http") {
|
|
return word
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Helper function to extract token from rclone config
|
|
func extractToken(configData string) string {
|
|
// Look for the token JSON in the config
|
|
lines := strings.Split(configData, "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "token") {
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) == 2 {
|
|
return strings.TrimSpace(parts[1])
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|