This commit is contained in:
2026-02-26 18:52:42 +01:00
parent 89f4f855c8
commit 11e8160cf9
11 changed files with 505 additions and 112 deletions

View File

@@ -2,32 +2,54 @@ package main
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
func uploadHandler(c *gin.Context) {
file, err := c.FormFile("file")
err := c.Request.ParseMultipartForm(0) // unlimited
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
src, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "No file uploaded"})
return
}
defer src.Close()
id := uuid.New().String()
delID := uuid.New().String()
// Secure the filename to prevent path traversal attacks
cleanName := filepath.Base(file.Filename)
storagePath := filepath.Join("uploads", id+"_"+cleanName)
cleanName := filepath.Base(header.Filename)
if err := c.SaveUploadedFile(file, storagePath); err != nil {
c.JSON(500, gin.H{"error": "Failed to save file"})
folderPath := filepath.Join("uploads", id)
os.MkdirAll(folderPath, 0755)
storagePath := filepath.Join(folderPath, cleanName)
dst, err := os.Create(storagePath)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer dst.Close()
written, err := io.Copy(dst, src)
fmt.Println("UPLOAD COMPLETE:", cleanName, written, "bytes")
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
expiry := time.Now().Add(time.Hour * 24) // Default 24h
expiry := time.Now().Add(24 * time.Hour)
record := FileRecord{
ID: id,
@@ -42,9 +64,8 @@ func uploadHandler(c *gin.Context) {
c.JSON(200, gin.H{
"id": id,
"deletion_id": delID,
"filename": cleanName,
"download_url": fmt.Sprintf("/f/%s", id),
"delete_url": fmt.Sprintf("/api/file/delete/%s", delID),
})
}
@@ -61,7 +82,9 @@ func downloadHandler(c *gin.Context) {
return
}
c.FileAttachment(record.Path, record.Filename)
//c.FileAttachment(record.Path, record.Filename)
c.Header("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, record.Filename))
c.File(record.Path)
db.Model(&record).Update("download_count", record.DownloadCount+1)
if record.DeleteAfterDownload {
@@ -78,3 +101,39 @@ func deleteHandler(c *gin.Context) {
performDeletion(&record)
c.JSON(200, gin.H{"message": "Deleted successfully"})
}
func loginHandler(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
var user User
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
c.HTML(401, "login.html", gin.H{"Error": true})
return
}
if bcrypt.CompareHashAndPassword(
[]byte(user.Password),
[]byte(password),
) != nil {
c.HTML(401, "login.html", gin.H{"Error": true})
return
}
token, _ := generateToken(username)
c.SetCookie(
"auth",
token,
86400,
"/",
"",
false,
true,
)
c.Redirect(302, "/admin")
}

View File

@@ -1,10 +1,13 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
@@ -19,24 +22,24 @@ func main() {
if err != nil {
log.Fatal("DB Connection failed:", err)
}
db.AutoMigrate(&FileRecord{})
db.AutoMigrate(&User{}, &FileRecord{})
ensureAdmin()
go cleanupWorker()
router := gin.Default()
router.MaxMultipartMemory = 100 << 20 // 100 MiB limit
router.MaxMultipartMemory = 10 << 30
router.LoadHTMLGlob("templates/*")
// Public Routes
router.GET("/", func(c *gin.Context) { c.HTML(200, "index.html", nil) })
router.POST("/api/upload", uploadHandler)
router.GET("/f/:id", downloadHandler)
router.DELETE("/api/file/:del_id", deleteHandler)
router.GET("/api/file/delete/:del_id", deleteHandler)
// Protected Admin Routes
admin := router.Group("/admin", gin.BasicAuth(gin.Accounts{
"admin": "password123", // CHANGE THIS
}))
router.POST("/api/upload", uploadHandler)
admin := router.Group("/admin")
admin.Use(authMiddleware())
admin.GET("/", func(c *gin.Context) {
var files []FileRecord
@@ -52,6 +55,65 @@ func main() {
c.Redirect(303, "/admin")
})
admin.GET("/download/:id", func(c *gin.Context) {
var record FileRecord
fmt.Printf("Getting file id: %v\n", c.Param("id"))
if err := db.First(&record, "id = ?", c.Param("id")).Error; err != nil {
fmt.Println("Admin download failed:", err)
c.Header("Content-Type", "text/plain")
c.String(418, "File not found")
return
}
fmt.Printf("Path: %s, Filename: %s\n", record.Path, record.Filename)
absPath, err := filepath.Abs(record.Path)
if err != nil {
c.String(500, "Path error")
return
}
fmt.Println("Serving:", absPath)
c.Header("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, record.Filename))
c.File(record.Path)
})
admin.GET("/files", func(c *gin.Context) {
var files []FileRecord
db.Order("created_at desc").Find(&files)
c.JSON(200, files)
})
router.POST("/login", loginHandler)
router.GET("/login", func(c *gin.Context) {
c.HTML(200, "login.html", nil)
})
router.GET("/logout", func(c *gin.Context) {
c.SetCookie("auth", "", -1, "/", "", false, true)
c.Redirect(302, "/")
})
log.Println("Server starting at http://localhost:8080")
router.Run(":8080")
}
func ensureAdmin() {
var count int64
db.Model(&User{}).Where("username = ?", "admin").Count(&count)
if count == 0 {
hash, _ := bcrypt.GenerateFromPassword(
[]byte("change_this_password"),
bcrypt.DefaultCost,
)
db.Create(&User{
Username: "admin",
Password: string(hash),
})
log.Println("Admin user created")
}
}

View File

@@ -13,3 +13,9 @@ type FileRecord struct {
Deleted bool `json:"deleted"`
CreatedAt time.Time `json:"created_at"`
}
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"unique"`
Password string
}

View File

@@ -1,14 +1,15 @@
package main
import (
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func performDeletion(r *FileRecord) {
r.Deleted = true
db.Save(r)
os.Remove(r.Path)
}
func cleanupWorker() {
@@ -21,3 +22,37 @@ func cleanupWorker() {
}
}
}
var jwtSecret = []byte("SUPER_SECRET_CHANGE_THIS")
func generateToken(username string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": username,
})
return token.SignedString(jwtSecret)
}
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenStr, err := c.Cookie("auth")
if err != nil {
c.Redirect(302, "/")
c.Abort()
return
}
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
c.Redirect(302, "/")
c.Abort()
return
}
c.Next()
}
}