repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
1e40160
parent
94d3e85
author
Eric Bower
date
2024-03-25 20:12:50 +0000 UTC
chore(analytics): hmac ip address
8 files changed,  +44, -7
A cmd/scripts/secret/main.go
+16, -0
 1@@ -0,0 +1,16 @@
 2+package main
 3+
 4+import (
 5+	"crypto/rand"
 6+	"encoding/hex"
 7+	"fmt"
 8+)
 9+
10+func main() {
11+	secret := make([]byte, 64)
12+	_, err := rand.Read(secret)
13+	if err != nil {
14+		panic(err)
15+	}
16+	fmt.Println(hex.EncodeToString(secret))
17+}
M pgs/api.go
+2, -2
 1@@ -220,7 +220,7 @@ func (h *AssetHandler) handle(w http.ResponseWriter, r *http.Request) {
 2 		)
 3 		// track 404s
 4 		ch := shared.GetAnalyticsQueue(r)
 5-		view, err := shared.AnalyticsVisitFromRequest(r, h.UserID)
 6+		view, err := shared.AnalyticsVisitFromRequest(r, h.UserID, h.Cfg.Secret)
 7 		if err == nil {
 8 			view.ProjectID = h.ProjectID
 9 			view.Status = http.StatusNotFound
10@@ -279,7 +279,7 @@ func (h *AssetHandler) handle(w http.ResponseWriter, r *http.Request) {
11 	if finContentType == "text/html" {
12 		// track visit
13 		ch := shared.GetAnalyticsQueue(r)
14-		view, err := shared.AnalyticsVisitFromRequest(r, h.UserID)
15+		view, err := shared.AnalyticsVisitFromRequest(r, h.UserID, h.Cfg.Secret)
16 		if err == nil {
17 			view.ProjectID = h.ProjectID
18 			ch <- view
M pgs/config.go
+5, -0
 1@@ -23,6 +23,10 @@ func NewConfigSite() *shared.ConfigSite {
 2 	minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
 3 	dbURL := shared.GetEnv("DATABASE_URL", "")
 4 	useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
 5+	secret := shared.GetEnv("PICO_SECRET", "")
 6+	if secret == "" {
 7+		panic("must provide PICO_SECRET environment variable")
 8+	}
 9 
10 	intro := "To create an account, enter a username.\n"
11 	intro += "After that, go to https://pico.sh/getting-started#next-steps"
12@@ -32,6 +36,7 @@ func NewConfigSite() *shared.ConfigSite {
13 		SubdomainsEnabled:    subdomains == "1",
14 		CustomdomainsEnabled: customdomains == "1",
15 		UseImgProxy:          useImgProxy == "1",
16+		Secret:               secret,
17 		ConfigCms: config.ConfigCms{
18 			Domain:      domain,
19 			Email:       email,
M prose/api.go
+2, -2
 1@@ -259,7 +259,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 2 
 3 	// track visit
 4 	ch := shared.GetAnalyticsQueue(r)
 5-	view, err := shared.AnalyticsVisitFromRequest(r, user.ID)
 6+	view, err := shared.AnalyticsVisitFromRequest(r, user.ID, cfg.Secret)
 7 	if err == nil {
 8 		ch <- view
 9 	} else {
10@@ -409,7 +409,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
11 		}
12 
13 		// track visit
14-		view, err := shared.AnalyticsVisitFromRequest(r, user.ID)
15+		view, err := shared.AnalyticsVisitFromRequest(r, user.ID, cfg.Secret)
16 		if err == nil {
17 			view.PostID = post.ID
18 			ch <- view
M prose/config.go
+5, -0
 1@@ -22,6 +22,10 @@ func NewConfigSite() *shared.ConfigSite {
 2 	useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
 3 	maxSize := uint64(500 * shared.MB)
 4 	maxImgSize := int64(10 * shared.MB)
 5+	secret := shared.GetEnv("PICO_SECRET", "")
 6+	if secret == "" {
 7+		panic("must provide PICO_SECRET environment variable")
 8+	}
 9 
10 	intro := "To get started, enter a username.\n"
11 	intro += "To learn next steps go to our docs at https://pico.sh/prose\n"
12@@ -31,6 +35,7 @@ func NewConfigSite() *shared.ConfigSite {
13 		SubdomainsEnabled:    subdomains == "1",
14 		CustomdomainsEnabled: customdomains == "1",
15 		UseImgProxy:          useImgProxy == "1",
16+		Secret:               secret,
17 		ConfigCms: config.ConfigCms{
18 			Domain:      domain,
19 			Email:       email,
M shared/analytics.go
+12, -2
 1@@ -1,6 +1,9 @@
 2 package shared
 3 
 4 import (
 5+	"crypto/hmac"
 6+	"crypto/sha256"
 7+	"encoding/hex"
 8 	"errors"
 9 	"fmt"
10 	"log/slog"
11@@ -13,6 +16,13 @@ import (
12 	"github.com/x-way/crawlerdetect"
13 )
14 
15+func hmacString(secret, data string) string {
16+	hmacer := hmac.New(sha256.New, []byte(secret))
17+	hmacer.Write([]byte(data))
18+	dataHmac := hmacer.Sum(nil)
19+	return hex.EncodeToString(dataHmac)
20+}
21+
22 func trackableRequest(r *http.Request) error {
23 	agent := r.UserAgent()
24 	// dont store requests from bots
25@@ -65,7 +75,7 @@ func cleanReferer(ref string) (string, error) {
26 
27 var ErrAnalyticsDisabled = errors.New("owner does not have site analytics enabled")
28 
29-func AnalyticsVisitFromRequest(r *http.Request, userID string) (*db.AnalyticsVisits, error) {
30+func AnalyticsVisitFromRequest(r *http.Request, userID string, secret string) (*db.AnalyticsVisits, error) {
31 	dbpool := GetDB(r)
32 	if !dbpool.HasFeatureForUser(userID, "analytics") {
33 		return nil, ErrAnalyticsDisabled
34@@ -91,7 +101,7 @@ func AnalyticsVisitFromRequest(r *http.Request, userID string) (*db.AnalyticsVis
35 		UserID:    userID,
36 		Host:      host,
37 		Path:      path,
38-		IpAddress: ipAddress,
39+		IpAddress: hmacString(secret, ipAddress),
40 		UserAgent: cleanUserAgent(r.UserAgent()),
41 		Referer:   referer,
42 		Status:    http.StatusOK,
M shared/config.go
+1, -0
1@@ -32,6 +32,7 @@ type ConfigSite struct {
2 	CustomdomainsEnabled bool
3 	SendgridKey          string
4 	UseImgProxy          bool
5+	Secret               string
6 }
7 
8 type CreateURL struct {
M sql/migrations/20240324_add_analytics_table.sql
+1, -1
1@@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS analytics_visits (
2   post_id uuid,
3   host varchar(253),
4   path varchar(2048),
5-  ip_address varchar(46),
6+  ip_address varchar(256),
7   user_agent varchar(1000),
8   referer varchar(253),
9   status int4,