diff --git a/cmd/main.go b/cmd/main.go index bbb762c..3b5ba58 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,5 +40,7 @@ func main() { http.HandleFunc("/upload", internal.AuthMiddleware(internal.UploadHandler)) http.HandleFunc("/health", internal.HealthHandler) + http.HandleFunc("/uploads/", internal.AuthClientMiddleware(internal.HandleFileServer("/uploads", "/uploads/"))) + http.ListenAndServe(":8080", nil) } diff --git a/config/env.go b/config/env.go index 195bbc3..91212e8 100644 --- a/config/env.go +++ b/config/env.go @@ -53,6 +53,20 @@ func LoadEnv() error { return scanner.Err() } +func requiredEnvs() { + if _, ok := os.LookupEnv(Prefix + "BearerToken"); !ok { + panic("BearerToken is required") + } + + if _, ok := os.LookupEnv(Prefix + "UploadDir"); !ok { + panic("UploadDir is required") + } + + if _, ok := os.LookupEnv(Prefix + "JWT_Secret"); !ok { + panic("JWT_Secret is required") + } +} + func setDefaultEnvs() { if _, ok := os.LookupEnv(Prefix + "UploadDir"); !ok { os.Setenv(Prefix+"UploadDir", "uploads/") diff --git a/go.mod b/go.mod index eaab518..9640221 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.neunzweinull.com/jan/CCTV go 1.22.2 + +require github.com/golang-jwt/jwt/v5 v5.2.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f56d3e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= diff --git a/internal/log.go b/internal/log.go index 2dcc9ca..041463c 100644 --- a/internal/log.go +++ b/internal/log.go @@ -66,6 +66,14 @@ func LogSuccessUpload(ip string, fileName string) { fmt.Print(logEntry) } +func LogFileServerRequest(ip string, fileName string) { + logEntry := fmt.Sprintf("[%s] File server request for %s from IP: %s\n", time.Now().Format(time.RFC3339), fileName, ip) + logFile.WriteString(logEntry) + logFile.Sync() + + fmt.Print(logEntry) +} + func CloseLog() { logFile.Close() } diff --git a/internal/server.go b/internal/server.go index 5d1efb1..dcead48 100644 --- a/internal/server.go +++ b/internal/server.go @@ -11,6 +11,8 @@ import ( "strings" "text/template" "time" + + "github.com/golang-jwt/jwt/v5" ) var ( @@ -19,8 +21,14 @@ var ( UploadDir = "" failedAttempts = make(map[string]int) bannedIPs = make(map[string]time.Time) + JWT_Secret = "" ) +type Claims struct { + Username string `json:"username"` + jwt.RegisteredClaims +} + func SetShit() { if maxAttempts, err := strconv.Atoi(os.Getenv("CCTV_MaxFailedAttempts")); err == nil { MaxFailedAttempts = maxAttempts @@ -28,6 +36,8 @@ func SetShit() { if banDuration, err := time.ParseDuration(os.Getenv("CCTV_BanDuration")); err == nil { BanDuration = banDuration * time.Minute } + + JWT_Secret = os.Getenv("CCTV_JWT_Secret") UploadDir = os.Getenv("CCTV_UploadDir") } @@ -62,7 +72,28 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { password := r.FormValue("password") if username == "admin" && password == "admin" { - w.Header().Set("Authorization", "Bearer "+"hihi") + exTime := time.Now().Add(5 * time.Minute) + claims := &Claims{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exTime), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(JWT_Secret)) + if err != nil { + http.Error(w, "Error signing token", http.StatusInternalServerError) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: tokenString, + Expires: exTime, + SameSite: http.SameSiteStrictMode, + Secure: true, + }) http.Redirect(w, r, "/dash", http.StatusSeeOther) return } else { @@ -85,13 +116,35 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { func AuthClientMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - fmt.Print(authHeader) - if !strings.HasPrefix(authHeader, "Bearer ") || strings.TrimPrefix(authHeader, "Bearer ") != "hihi" { + authCookie, err := r.Cookie("token") + if err != nil { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + claims := &Claims{} + token, err := jwt.ParseWithClaims(authCookie.Value, claims, func(token *jwt.Token) (interface{}, error) { + return []byte(JWT_Secret), nil + }) + + if err != nil || !token.Valid { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } + exTime := time.Now().Add(5 * time.Minute) + claims.ExpiresAt = jwt.NewNumericDate(exTime) + tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(JWT_Secret)) + if err == nil { + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: tokenString, + Expires: exTime, + SameSite: http.SameSiteStrictMode, + Secure: true, + }) + } + next(w, r) } } @@ -218,6 +271,15 @@ func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { } +func HandleFileServer(dir, prefix string) http.HandlerFunc { + fs := http.FileServer(http.Dir(dir)) + realHandler := http.StripPrefix(prefix, fs).ServeHTTP + return func(w http.ResponseWriter, r *http.Request) { + LogFileServerRequest(getClientIP(r), r.URL.Path) + realHandler(w, r) + } +} + func HealthHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }