10
flake.nix
10
flake.nix
@@ -15,10 +15,10 @@
|
|||||||
packages = rec {
|
packages = rec {
|
||||||
slenpaste = pkgs.buildGoModule {
|
slenpaste = pkgs.buildGoModule {
|
||||||
pname = "slenpaste";
|
pname = "slenpaste";
|
||||||
version = "0.1.2";
|
version = "0.1.3";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
goPackagePath = "github.com/slendidev/slenpaste";
|
goPackagePath = "github.com/slendidev/slenpaste";
|
||||||
vendorHash = null;
|
vendorHash = "sha256-MUvodL6K71SCfxu51T/Ka2/w32Kz+IXem1bYqXQLSaU=";
|
||||||
};
|
};
|
||||||
default = slenpaste;
|
default = slenpaste;
|
||||||
};
|
};
|
||||||
@@ -55,6 +55,11 @@
|
|||||||
default = false;
|
default = false;
|
||||||
description = "Whether to expire on first view";
|
description = "Whether to expire on first view";
|
||||||
};
|
};
|
||||||
|
options.services.slenpaste.https = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to use https:// in generated URLs";
|
||||||
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.services.slenpaste.enable {
|
config = lib.mkIf config.services.slenpaste.enable {
|
||||||
systemd.services.slenpaste = {
|
systemd.services.slenpaste = {
|
||||||
@@ -68,6 +73,7 @@
|
|||||||
-listen "${config.services.slenpaste.listen}" \
|
-listen "${config.services.slenpaste.listen}" \
|
||||||
-expire "${config.services.slenpaste.expireDur}" \
|
-expire "${config.services.slenpaste.expireDur}" \
|
||||||
${lib.optionalString config.services.slenpaste.expireOnView "-expire-on-view=false"}
|
${lib.optionalString config.services.slenpaste.expireOnView "-expire-on-view=false"}
|
||||||
|
${lib.optionalString config.services.slenpaste.https "-https"}
|
||||||
'';
|
'';
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
};
|
};
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,3 +1,5 @@
|
|||||||
module github.com/slendidev/slenpaste
|
module github.com/slendidev/slenpaste
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
|
require golang.org/x/time v0.11.0 // indirect
|
||||||
|
|||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
42
main.go
42
main.go
@@ -12,6 +12,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"sync"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -20,6 +22,9 @@ var (
|
|||||||
staticDir string
|
staticDir string
|
||||||
expireDur time.Duration
|
expireDur time.Duration
|
||||||
expireOnView bool
|
expireOnView bool
|
||||||
|
limiters = make(map[string]*rate.Limiter)
|
||||||
|
limMu sync.Mutex
|
||||||
|
useHTTPS bool
|
||||||
)
|
)
|
||||||
|
|
||||||
type meta struct {
|
type meta struct {
|
||||||
@@ -27,6 +32,32 @@ type meta struct {
|
|||||||
ExpireOnView bool `json:"expire_on_view"`
|
ExpireOnView bool `json:"expire_on_view"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLimiter(ip string) *rate.Limiter {
|
||||||
|
limMu.Lock()
|
||||||
|
defer limMu.Unlock()
|
||||||
|
lim, ok := limiters[ip]
|
||||||
|
if !ok {
|
||||||
|
lim = rate.NewLimiter(1, 5) // 1 req/sec, burst of 5
|
||||||
|
limiters[ip] = lim
|
||||||
|
}
|
||||||
|
return lim
|
||||||
|
}
|
||||||
|
|
||||||
|
func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ip := r.RemoteAddr
|
||||||
|
if i := strings.LastIndex(ip, ":"); i != -1 {
|
||||||
|
ip = ip[:i]
|
||||||
|
}
|
||||||
|
lim := getLimiter(ip)
|
||||||
|
if !lim.Allow() {
|
||||||
|
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func randomID(n int) string {
|
func randomID(n int) string {
|
||||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
b := make([]rune, n)
|
b := make([]rune, n)
|
||||||
@@ -140,7 +171,11 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
_ = os.WriteFile(path+".json", metaBytes, 0644)
|
_ = os.WriteFile(path+".json", metaBytes, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w, "http://%s/%s\n", domain, filename)
|
scheme := "http"
|
||||||
|
if useHTTPS {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s://%s/%s\n", scheme, domain, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -192,15 +227,16 @@ func main() {
|
|||||||
flag.StringVar(&staticDir, "static", "static", "directory to save pastes")
|
flag.StringVar(&staticDir, "static", "static", "directory to save pastes")
|
||||||
flag.DurationVar(&expireDur, "expire", 0, "time after which paste expires (e.g. 5m, 1h)")
|
flag.DurationVar(&expireDur, "expire", 0, "time after which paste expires (e.g. 5m, 1h)")
|
||||||
flag.BoolVar(&expireOnView, "expire-on-view", false, "delete paste after it's viewed once")
|
flag.BoolVar(&expireOnView, "expire-on-view", false, "delete paste after it's viewed once")
|
||||||
|
flag.BoolVar(&useHTTPS, "https", false, "use https:// in generated URLs")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", rateLimitMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
uploadHandler(w, r)
|
uploadHandler(w, r)
|
||||||
} else {
|
} else {
|
||||||
viewHandler(w, r)
|
viewHandler(w, r)
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir)
|
fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir)
|
||||||
http.ListenAndServe(listenAddr, nil)
|
http.ListenAndServe(listenAddr, nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user