Implement user-specific share link management for files

This commit is contained in:
Leon Bösche
2026-01-24 22:32:58 +01:00
parent acfd882bba
commit 228a5c9644
3 changed files with 175 additions and 10 deletions

Binary file not shown.

View File

@@ -210,6 +210,16 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
r.Get("/user/files/{fileId}/collabora-proxy", func(w http.ResponseWriter, req *http.Request) {
collaboraProxyHandler(w, req, db, jwtManager, "https://of.b0esche.cloud")
})
// Share link management for user files
r.Get("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
getUserFileShareLinkHandler(w, req, db)
})
r.Post("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
createUserFileShareLinkHandler(w, req, db)
})
r.Delete("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
revokeUserFileShareLinkHandler(w, req, db)
})
// Org routes
r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) {
@@ -3028,3 +3038,156 @@ func publicFileDownloadHandler(w http.ResponseWriter, r *http.Request, db *datab
// Copy body
io.Copy(w, resp.Body)
}
func getUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
userIDStr, _ := middleware.GetUserID(r.Context())
userID, _ := uuid.Parse(userIDStr)
fileId := chi.URLParam(r, "fileId")
fileUUID, err := uuid.Parse(fileId)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
return
}
// Check if file exists and belongs to user
file, err := db.GetFileByID(r.Context(), fileUUID)
if err != nil {
errors.LogError(r, err, "Failed to get file")
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
if file.UserID == nil || *file.UserID != userID {
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
link, err := db.GetFileShareLinkByFileID(r.Context(), fileUUID)
if err != nil {
if err == sql.ErrNoRows {
// No share link exists
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"exists": false,
})
return
}
errors.LogError(r, err, "Failed to get share link")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
// Build full URL
scheme := "https"
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
scheme = proto
} else if r.TLS == nil {
scheme = "http"
}
host := r.Host
if host == "" {
host = "go.b0esche.cloud"
}
fullURL := fmt.Sprintf("%s://%s/public/share/%s", scheme, host, link.Token)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"exists": true,
"url": fullURL,
"token": link.Token,
})
}
func createUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
userIDStr, _ := middleware.GetUserID(r.Context())
userID, _ := uuid.Parse(userIDStr)
fileId := chi.URLParam(r, "fileId")
fileUUID, err := uuid.Parse(fileId)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
return
}
// Check if file exists and belongs to user
file, err := db.GetFileByID(r.Context(), fileUUID)
if err != nil {
errors.LogError(r, err, "Failed to get file")
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
if file.UserID == nil || *file.UserID != userID {
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
// Revoke existing link if any
db.RevokeFileShareLink(r.Context(), fileUUID) // Ignore error
// Generate token
token, err := storage.GenerateSecurePassword(32)
if err != nil {
errors.LogError(r, err, "Failed to generate token")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
link, err := db.CreateFileShareLink(r.Context(), token, fileUUID, userID, userID)
if err != nil {
errors.LogError(r, err, "Failed to create share link")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
// Build full URL
scheme := "https"
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
scheme = proto
} else if r.TLS == nil {
scheme = "http"
}
host := r.Host
if host == "" {
host = "go.b0esche.cloud"
}
fullURL := fmt.Sprintf("%s://%s/public/share/%s", scheme, host, link.Token)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"url": fullURL,
"token": link.Token,
})
}
func revokeUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
userIDStr, _ := middleware.GetUserID(r.Context())
userID, _ := uuid.Parse(userIDStr)
fileId := chi.URLParam(r, "fileId")
fileUUID, err := uuid.Parse(fileId)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
return
}
// Check if file exists and belongs to user
file, err := db.GetFileByID(r.Context(), fileUUID)
if err != nil {
errors.LogError(r, err, "Failed to get file")
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
if file.UserID == nil || *file.UserID != userID {
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
err = db.RevokeFileShareLink(r.Context(), fileUUID)
if err != nil {
errors.LogError(r, err, "Failed to revoke share link")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}