From 11e8160cf9ce28293ab83c4f40ead5643bfaeb6f Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 26 Feb 2026 18:52:42 +0100 Subject: [PATCH] fix alot --- Dockerfile | 27 +++++ go.mod | 15 +-- go.sum | 16 +++ src/handlers.go | 79 +++++++++++++-- src/main.go | 78 +++++++++++++-- src/models.go | 6 ++ src/utils.go | 39 +++++++- templates/admin.html | 8 +- templates/download.html | 2 +- templates/index.html | 217 +++++++++++++++++++++++++--------------- templates/login.html | 130 ++++++++++++++++++++++++ 11 files changed, 505 insertions(+), 112 deletions(-) create mode 100644 Dockerfile create mode 100644 templates/login.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..748e4bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +RUN apk add --no-cache git bash + +COPY src/go.mod src/go.sum ./ +RUN go mod download + +COPY src/ . + +RUN go build -o server . + +FROM alpine:latest + +RUN apk add --no-cache ca-certificates bash + +WORKDIR /app + +COPY --from=builder /app/server . + +COPY --from=builder /app/templates ./templates +COPY --from=builder /app/uploads ./uploads + +EXPOSE 8080 + +CMD ["./server"] \ No newline at end of file diff --git a/go.mod b/go.mod index 2c8e839..338623e 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -31,13 +32,13 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gorm.io/driver/sqlite v1.6.0 // indirect gorm.io/gorm v1.31.1 // indirect diff --git a/go.sum b/go.sum index d7dcaea..946ca82 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -67,21 +69,35 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/handlers.go b/src/handlers.go index 378a3e4..62bf2c5 100644 --- a/src/handlers.go +++ b/src/handlers.go @@ -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") +} diff --git a/src/main.go b/src/main.go index 41afe57..8733ac4 100644 --- a/src/main.go +++ b/src/main.go @@ -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") + } +} diff --git a/src/models.go b/src/models.go index c385441..74ef47b 100644 --- a/src/models.go +++ b/src/models.go @@ -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 +} diff --git a/src/utils.go b/src/utils.go index 73ebc5d..9b70f89 100644 --- a/src/utils.go +++ b/src/utils.go @@ -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() + } +} diff --git a/templates/admin.html b/templates/admin.html index ddc5cff..a686c26 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -3,7 +3,7 @@ - GhostShare | Admin + Admin -
-
-

Send it

-
+
-
-
- +
+
+

Send it

+
-
- Click to select or drop file +
+
+ + +
+ Click to select or drop file +
+ + + +
- +
+
+ + +
- +
+ + +
+ + + +
-
-
- - + -
+SUDO \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..4b726b7 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,130 @@ + + + + + + Login + + + + + + +
+ +
+

+ System Access +

+ + + ← BACK + +
+ + +
+ + {{if .Error}} +
+ ACCESS DENIED +
+ {{end}} + +
+ +
+
Username
+ +
+ +
+
Password
+ +
+ +
+ +
+ +
+ +
+
+ + + \ No newline at end of file