Implement user provisioning for Nextcloud accounts and enhance WebDAV client handling
This commit is contained in:
BIN
go_cloud/api
BIN
go_cloud/api
Binary file not shown.
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -28,10 +29,55 @@ import (
|
||||
"go.b0esche.cloud/backend/internal/storage"
|
||||
)
|
||||
|
||||
// getUserWebDAVClient gets or creates a user's Nextcloud account and returns a WebDAV client for them
|
||||
func getUserWebDAVClient(ctx context.Context, db *database.DB, userID uuid.UUID, nextcloudBaseURL, adminUser, adminPass string) (*storage.WebDAVClient, error) {
|
||||
var user struct {
|
||||
NextcloudUsername string
|
||||
NextcloudPassword string
|
||||
Email string
|
||||
}
|
||||
|
||||
err := db.QueryRowContext(ctx,
|
||||
"SELECT nextcloud_username, nextcloud_password, email FROM users WHERE id = $1",
|
||||
userID).Scan(&user.NextcloudUsername, &user.NextcloudPassword, &user.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
// If user doesn't have Nextcloud credentials, create them
|
||||
if user.NextcloudUsername == "" || user.NextcloudPassword == "" {
|
||||
// Use email prefix as username
|
||||
ncUsername := strings.Split(user.Email, "@")[0]
|
||||
ncPassword, err := storage.GenerateSecurePassword(32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate password: %w", err)
|
||||
}
|
||||
|
||||
// Create Nextcloud user account
|
||||
err = storage.CreateNextcloudUser(nextcloudBaseURL, adminUser, adminPass, ncUsername, ncPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Nextcloud user: %w", err)
|
||||
}
|
||||
|
||||
// Update database with Nextcloud credentials
|
||||
_, err = db.ExecContext(ctx,
|
||||
"UPDATE users SET nextcloud_username = $1, nextcloud_password = $2 WHERE id = $3",
|
||||
ncUsername, ncPassword, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update user credentials: %w", err)
|
||||
}
|
||||
|
||||
user.NextcloudUsername = ncUsername
|
||||
user.NextcloudPassword = ncPassword
|
||||
fmt.Printf("[AUTO-PROVISION] Created Nextcloud account for user %s: %s\n", userID, ncUsername)
|
||||
}
|
||||
|
||||
// Create user-specific WebDAV client
|
||||
return storage.NewUserWebDAVClient(nextcloudBaseURL, user.NextcloudUsername, user.NextcloudPassword), nil
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, authService *auth.Service, auditLogger *audit.Logger) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
// optional WebDAV/Nextcloud client
|
||||
storageClient := storage.NewWebDAVClient(cfg)
|
||||
|
||||
// Global middleware
|
||||
r.Use(middleware.RequestID)
|
||||
@@ -87,18 +133,18 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
||||
})
|
||||
// Download user file
|
||||
r.Get("/user/files/download", func(w http.ResponseWriter, req *http.Request) {
|
||||
downloadUserFileHandler(w, req, db, storageClient)
|
||||
downloadUserFileHandler(w, req, db, cfg)
|
||||
})
|
||||
// Create / delete in user workspace
|
||||
r.Post("/user/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
createUserFileHandler(w, req, db, auditLogger, storageClient)
|
||||
createUserFileHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
r.Delete("/user/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
deleteUserFileHandler(w, req, db, auditLogger, storageClient)
|
||||
deleteUserFileHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
// POST wrapper for delete
|
||||
r.Post("/user/files/delete", func(w http.ResponseWriter, req *http.Request) {
|
||||
deleteUserFilePostHandler(w, req, db, auditLogger, storageClient)
|
||||
deleteUserFilePostHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
|
||||
// Org routes
|
||||
@@ -119,21 +165,21 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
||||
})
|
||||
// Download org file
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileRead)).Get("/files/download", func(w http.ResponseWriter, req *http.Request) {
|
||||
downloadOrgFileHandler(w, req, db, storageClient)
|
||||
downloadOrgFileHandler(w, req, db, cfg)
|
||||
})
|
||||
|
||||
// Create file/folder in org workspace
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Post("/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
createOrgFileHandler(w, req, db, auditLogger, storageClient)
|
||||
createOrgFileHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
// Also accept POST delete for clients that cannot send DELETE with body
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Post("/files/delete", func(w http.ResponseWriter, req *http.Request) {
|
||||
deleteOrgFilePostHandler(w, req, db, auditLogger, storageClient)
|
||||
deleteOrgFilePostHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
|
||||
// Delete file/folder in org workspace (body: {"path":"/path"})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Delete("/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
deleteOrgFileHandler(w, req, db, auditLogger, storageClient)
|
||||
deleteOrgFileHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
r.Route("/files/{fileId}", func(r chi.Router) {
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -1020,7 +1066,7 @@ func userFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||
}
|
||||
|
||||
// createOrgFileHandler creates a file or folder record for an org workspace.
|
||||
func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
orgID := r.Context().Value("org").(uuid.UUID)
|
||||
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||
userID, _ := uuid.Parse(userIDStr)
|
||||
@@ -1048,7 +1094,7 @@ func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read file into memory (so we can attempt WebDAV upload and fallback to disk)
|
||||
// Read file into memory
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to read uploaded file")
|
||||
@@ -1056,19 +1102,21 @@ func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
||||
return
|
||||
}
|
||||
|
||||
// ONLY use Nextcloud WebDAV storage
|
||||
storedPath := filepath.ToSlash(filepath.Join(parentPath, header.Filename))
|
||||
if !strings.HasPrefix(storedPath, "/") {
|
||||
storedPath = "/" + storedPath
|
||||
}
|
||||
written := int64(len(data))
|
||||
|
||||
if storageClient == nil {
|
||||
// Get or create user's WebDAV client
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client")
|
||||
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Build remote path under /orgs/<orgId>
|
||||
// Upload to user's Nextcloud space under /orgs/<orgID>/
|
||||
rel := strings.TrimPrefix(storedPath, "/")
|
||||
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||
if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil {
|
||||
@@ -1125,7 +1173,7 @@ func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
||||
}
|
||||
|
||||
// deleteOrgFileHandler deletes a file/folder in org workspace by path
|
||||
func deleteOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
func deleteOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
orgID := r.Context().Value("org").(uuid.UUID)
|
||||
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||
userID, _ := uuid.Parse(userIDStr)
|
||||
@@ -1138,8 +1186,11 @@ func deleteOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
||||
return
|
||||
}
|
||||
|
||||
// Delete from Nextcloud if configured
|
||||
if storageClient != nil {
|
||||
// Get or create user's WebDAV client and delete from Nextcloud
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client (continuing with database deletion)")
|
||||
} else {
|
||||
rel := strings.TrimPrefix(req.Path, "/")
|
||||
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||
if err := storageClient.Delete(r.Context(), remotePath); err != nil {
|
||||
@@ -1166,12 +1217,12 @@ func deleteOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
||||
}
|
||||
|
||||
// Also accept POST /orgs/{orgId}/files/delete for clients that cannot send DELETE with body
|
||||
func deleteOrgFilePostHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
deleteOrgFileHandler(w, r, db, auditLogger, storageClient)
|
||||
func deleteOrgFilePostHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
deleteOrgFileHandler(w, r, db, auditLogger, cfg)
|
||||
}
|
||||
|
||||
// createUserFileHandler creates a file or folder record for the authenticated user's personal workspace.
|
||||
func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
if !ok || userIDStr == "" {
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
@@ -1199,7 +1250,7 @@ func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
// Read file into memory to allow WebDAV upload and disk fallback
|
||||
// Read file into memory to allow WebDAV upload
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to read uploaded file")
|
||||
@@ -1213,16 +1264,18 @@ func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
written := int64(len(data))
|
||||
fmt.Printf("[DEBUG] Upload: user=%s, file=%s, size=%d, path=%s\n", userID.String(), header.Filename, len(data), storedPath)
|
||||
|
||||
// ONLY use Nextcloud WebDAV storage
|
||||
if storageClient == nil {
|
||||
// Get or create user's WebDAV client
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client")
|
||||
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rel := strings.TrimPrefix(storedPath, "/")
|
||||
remotePath := path.Join("/users", userID.String(), rel)
|
||||
fmt.Printf("[DEBUG] Uploading to WebDAV: %s\n", remotePath)
|
||||
if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil {
|
||||
// Upload to user's personal Nextcloud space (just the path, no username prefix)
|
||||
remotePath := strings.TrimPrefix(storedPath, "/")
|
||||
fmt.Printf("[DEBUG] Uploading to user WebDAV: /%s\n", remotePath)
|
||||
if err = storageClient.Upload(r.Context(), "/"+remotePath, bytes.NewReader(data), int64(len(data))); err != nil {
|
||||
errors.LogError(r, err, "WebDAV upload failed")
|
||||
errors.WriteError(w, errors.CodeInternal, "Failed to upload file to storage", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -1275,12 +1328,12 @@ func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
}
|
||||
|
||||
// Also accept POST /user/files/delete
|
||||
func deleteUserFilePostHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
deleteUserFileHandler(w, r, db, auditLogger, storageClient)
|
||||
func deleteUserFilePostHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
deleteUserFileHandler(w, r, db, auditLogger, cfg)
|
||||
}
|
||||
|
||||
// deleteUserFileHandler deletes a file/folder in user's personal workspace by path
|
||||
func deleteUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, storageClient *storage.WebDAVClient) {
|
||||
func deleteUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
if !ok || userIDStr == "" {
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
@@ -1296,12 +1349,13 @@ func deleteUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
return
|
||||
}
|
||||
|
||||
// Delete from Nextcloud if configured
|
||||
if storageClient != nil {
|
||||
rel := strings.TrimPrefix(req.Path, "/")
|
||||
// Keep remote user workspace path consistent with uploads: "/users/<userID>/<rel>"
|
||||
remotePath := path.Join("/users", userID.String(), rel)
|
||||
if err := storageClient.Delete(r.Context(), remotePath); err != nil {
|
||||
// Get or create user's WebDAV client and delete from Nextcloud
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client (continuing with database deletion)")
|
||||
} else {
|
||||
remotePath := strings.TrimPrefix(req.Path, "/")
|
||||
if err := storageClient.Delete(r.Context(), "/"+remotePath); err != nil {
|
||||
errors.LogError(r, err, "Failed to delete from Nextcloud (continuing anyway)")
|
||||
}
|
||||
}
|
||||
@@ -1324,8 +1378,10 @@ func deleteUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
}
|
||||
|
||||
// downloadOrgFileHandler downloads a file from org workspace
|
||||
func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, storageClient *storage.WebDAVClient) {
|
||||
func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config) {
|
||||
orgID := r.Context().Value("org").(uuid.UUID)
|
||||
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||
userID, _ := uuid.Parse(userIDStr)
|
||||
|
||||
filePath := r.URL.Query().Get("path")
|
||||
if filePath == "" {
|
||||
@@ -1333,15 +1389,17 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
|
||||
return
|
||||
}
|
||||
|
||||
// ONLY use Nextcloud WebDAV storage
|
||||
if storageClient == nil {
|
||||
// Get or create user's WebDAV client
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client")
|
||||
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Download from user's Nextcloud space under /orgs/<orgID>/
|
||||
rel := strings.TrimPrefix(filePath, "/")
|
||||
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||
|
||||
reader, size, err := storageClient.Download(r.Context(), remotePath)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to download from Nextcloud")
|
||||
@@ -1373,7 +1431,7 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
|
||||
}
|
||||
|
||||
// downloadUserFileHandler downloads a file from user's personal workspace
|
||||
func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, storageClient *storage.WebDAVClient) {
|
||||
func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config) {
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
if !ok || userIDStr == "" {
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
@@ -1390,18 +1448,19 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
// Log the requested file path for debugging
|
||||
fmt.Printf("[DEBUG] Download request - User: %s, Path: %s\n", userID.String(), filePath)
|
||||
|
||||
// ONLY use Nextcloud WebDAV storage
|
||||
if storageClient == nil {
|
||||
// Get or create user's WebDAV client
|
||||
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get user WebDAV client")
|
||||
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rel := strings.TrimPrefix(filePath, "/")
|
||||
// Keep remote user workspace path consistent with uploads: "/users/<userID>/<rel>"
|
||||
remotePath := path.Join("/users", userID.String(), rel)
|
||||
fmt.Printf("[DEBUG] Trying WebDAV path: %s\n", remotePath)
|
||||
// Download from user's personal Nextcloud space
|
||||
remotePath := strings.TrimPrefix(filePath, "/")
|
||||
fmt.Printf("[DEBUG] Downloading from user WebDAV: /%s\n", remotePath)
|
||||
|
||||
reader, size, err := storageClient.Download(r.Context(), remotePath)
|
||||
reader, size, err := storageClient.Download(r.Context(), "/"+remotePath)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to download from Nextcloud")
|
||||
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||
|
||||
77
go_cloud/internal/storage/nextcloud.go
Normal file
77
go_cloud/internal/storage/nextcloud.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateNextcloudUser creates a new Nextcloud user account via OCS API
|
||||
func CreateNextcloudUser(nextcloudBaseURL, adminUser, adminPass, username, password string) error {
|
||||
// Remove any path from base URL, we need just the scheme://host:port
|
||||
baseURL := strings.Split(nextcloudBaseURL, "/remote.php")[0]
|
||||
url := fmt.Sprintf("%s/ocs/v1.php/cloud/users", baseURL)
|
||||
|
||||
payload := map[string]string{
|
||||
"userid": username,
|
||||
"password": password,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.SetBasicAuth(adminUser, adminPass)
|
||||
req.Header.Set("OCS-APIRequest", "true")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
// 200 = success, 409 = user already exists (which is fine)
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 409 {
|
||||
return fmt.Errorf("failed to create Nextcloud user (status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
fmt.Printf("[NEXTCLOUD] Created user account: %s\n", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateSecurePassword generates a random secure password
|
||||
func GenerateSecurePassword(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(bytes)[:length], nil
|
||||
}
|
||||
|
||||
// NewUserWebDAVClient creates a WebDAV client for a specific user
|
||||
func NewUserWebDAVClient(nextcloudBaseURL, username, password string) *WebDAVClient {
|
||||
baseURL := fmt.Sprintf("%s/remote.php/dav/files/%s", nextcloudBaseURL, username)
|
||||
|
||||
return &WebDAVClient{
|
||||
baseURL: baseURL,
|
||||
user: username,
|
||||
pass: password,
|
||||
basePrefix: "/",
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user