dashboard / pico / fix: address CVE-2024-45337 #39 rss

accepted · opened on 2024-12-11T20:00:24Z by erock
Help
# add changes to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add 39
# add review to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add --review 39
# remove patchset
ssh pr.pico.sh ps rm ps-x
# checkout all patches
ssh pr.pico.sh pr print 39 | git am -3
# print a diff between the last two patches in a patch request
ssh pr.pico.sh pr diff 39
# accept PR
ssh pr.pico.sh pr accept 39
# close PR
ssh pr.pico.sh pr close 39

Logs

erock created pr with ps-85 on 2024-12-11T20:00:24Z
erock changed status on 2024-12-20T18:22:22Z {"status":"accepted"}

Patchsets

ps-85 by erock on 2024-12-11T20:00:24Z

fix: address CVE-2024-45337

Reference: https://github.com/golang/go/issues/70779
filehandlers/imgs/handler.go link
+4 -7
 1diff --git a/filehandlers/imgs/handler.go b/filehandlers/imgs/handler.go
 2index b8354c3..669f479 100644
 3--- a/filehandlers/imgs/handler.go
 4+++ b/filehandlers/imgs/handler.go
 5@@ -48,7 +48,7 @@ func NewUploadImgHandler(dbpool db.DB, cfg *shared.ConfigSite, storage storage.S
 6 }
 7 
 8 func (h *UploadImgHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReaderAtCloser, error) {
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return nil, nil, err
13 	}
14@@ -88,7 +88,7 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.F
15 
16 func (h *UploadImgHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
17 	logger := h.Cfg.Logger
18-	user, err := shared.GetUser(s.Context())
19+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
20 	if err != nil {
21 		logger.Error("could not get user from ctx", "err", err.Error())
22 		return "", err
23@@ -145,10 +145,7 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (str
24 		logger.Info("unable to find image, continuing", "filename", nextPost.Filename, "err", err.Error())
25 	}
26 
27-	featureFlag, err := shared.GetFeatureFlag(s.Context())
28-	if err != nil {
29-		return "", err
30-	}
31+	featureFlag := shared.FindPlusFF(h.DBPool, h.Cfg, user.ID)
32 	metadata := PostMetaData{
33 		OrigText:    text,
34 		Post:        &nextPost,
35@@ -192,7 +189,7 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (str
36 }
37 
38 func (h *UploadImgHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
39-	user, err := shared.GetUser(s.Context())
40+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
41 	if err != nil {
42 		return err
43 	}
filehandlers/imgs/img.go link
+1 -1
 1diff --git a/filehandlers/imgs/img.go b/filehandlers/imgs/img.go
 2index 45c44a8..23cc0aa 100644
 3--- a/filehandlers/imgs/img.go
 4+++ b/filehandlers/imgs/img.go
 5@@ -80,7 +80,7 @@ func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
 6 	if !valid {
 7 		return err
 8 	}
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return err
13 	}
filehandlers/post_handler.go link
+3 -3
 1diff --git a/filehandlers/post_handler.go b/filehandlers/post_handler.go
 2index 63f57c0..63dd861 100644
 3--- a/filehandlers/post_handler.go
 4+++ b/filehandlers/post_handler.go
 5@@ -47,7 +47,7 @@ func NewScpPostHandler(dbpool db.DB, cfg *shared.ConfigSite, hooks ScpFileHooks,
 6 }
 7 
 8 func (h *ScpUploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReaderAtCloser, error) {
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return nil, nil, err
13 	}
14@@ -76,7 +76,7 @@ func (h *ScpUploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.F
15 
16 func (h *ScpUploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
17 	logger := h.Cfg.Logger
18-	user, err := shared.GetUser(s.Context())
19+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
20 	if err != nil {
21 		logger.Error("error getting user from ctx", "err", err.Error())
22 		return "", err
23@@ -263,7 +263,7 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (str
24 
25 func (h *ScpUploadHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
26 	logger := h.Cfg.Logger
27-	user, err := shared.GetUser(s.Context())
28+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
29 	if err != nil {
30 		logger.Error("could not get user from ctx", "err", err.Error())
31 		return err
filehandlers/router_handler.go link
+2 -2
 1diff --git a/filehandlers/router_handler.go b/filehandlers/router_handler.go
 2index 9b7be13..241500b 100644
 3--- a/filehandlers/router_handler.go
 4+++ b/filehandlers/router_handler.go
 5@@ -82,7 +82,7 @@ func (r *FileHandlerRouter) Read(s ssh.Session, entry *utils.FileEntry) (os.File
 6 
 7 func BaseList(s ssh.Session, fpath string, isDir bool, recursive bool, spaces []string, dbpool db.DB) ([]os.FileInfo, error) {
 8 	var fileList []os.FileInfo
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := dbpool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return fileList, err
13 	}
14@@ -153,7 +153,7 @@ func (r *FileHandlerRouter) GetLogger() *slog.Logger {
15 }
16 
17 func (r *FileHandlerRouter) Validate(s ssh.Session) error {
18-	user, err := shared.GetUser(s.Context())
19+	user, err := r.DBPool.FindUser(s.Permissions().Extensions["user_id"])
20 	if err != nil {
21 		return err
22 	}
go.mod link
+5 -5
 1diff --git a/go.mod b/go.mod
 2index 9df38dd..9b880a8 100644
 3--- a/go.mod
 4+++ b/go.mod
 5@@ -53,7 +53,7 @@ require (
 6 	go.abhg.dev/goldmark/anchor v0.1.1
 7 	go.abhg.dev/goldmark/hashtag v0.3.1
 8 	go.abhg.dev/goldmark/toc v0.10.0
 9-	golang.org/x/crypto v0.29.0
10+	golang.org/x/crypto v0.31.0
11 	gopkg.in/yaml.v2 v2.4.0
12 )
13 
14@@ -179,10 +179,10 @@ require (
15 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
16 	golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
17 	golang.org/x/net v0.31.0 // indirect
18-	golang.org/x/sync v0.9.0 // indirect
19-	golang.org/x/sys v0.27.0 // indirect
20-	golang.org/x/term v0.26.0 // indirect
21-	golang.org/x/text v0.20.0 // indirect
22+	golang.org/x/sync v0.10.0 // indirect
23+	golang.org/x/sys v0.28.0 // indirect
24+	golang.org/x/term v0.27.0 // indirect
25+	golang.org/x/text v0.21.0 // indirect
26 	golang.org/x/time v0.8.0 // indirect
27 	google.golang.org/protobuf v1.35.2 // indirect
28 	mvdan.cc/xurls/v2 v2.5.0 // indirect
go.sum link
+10 -10
 1diff --git a/go.sum b/go.sum
 2index 9a04601..af1da56 100644
 3--- a/go.sum
 4+++ b/go.sum
 5@@ -391,8 +391,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 6 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 7 golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
 8 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 9-golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
10-golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
11+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
12+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
13 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
14 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
15 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
16@@ -417,8 +417,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
17 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
18 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
19 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
20-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
21-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
22+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
23+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
24 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
25 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
26 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
27@@ -438,8 +438,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
28 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
30 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
31-golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
32-golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
33+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
34+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
35 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
36 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
37 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
38@@ -447,16 +447,16 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
39 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
40 golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
41 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
42-golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
43-golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
44+golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
45+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
46 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
47 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
48 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
49 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
50 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
51 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
52-golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
53-golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
54+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
55+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
56 golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
57 golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
58 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
pgs/tunnel.go link
+11 -29
 1diff --git a/pgs/tunnel.go b/pgs/tunnel.go
 2index e4e59bb..547a4b3 100644
 3--- a/pgs/tunnel.go
 4+++ b/pgs/tunnel.go
 5@@ -7,7 +7,6 @@ import (
 6 	"github.com/charmbracelet/ssh"
 7 	"github.com/picosh/pico/db"
 8 	"github.com/picosh/pico/shared"
 9-	"github.com/picosh/utils"
10 )
11 
12 type TunnelWebRouter struct {
13@@ -39,16 +38,14 @@ func createHttpHandler(apiConfig *shared.ApiConfig) CtxHttpBridge {
14 			"impersonating", asUser,
15 		)
16 
17-		pubkey, err := shared.GetPublicKey(ctx)
18-		if err != nil {
19-			log.Error(err.Error(), "subdomain", subdomain)
20+		pubkey := ctx.Permissions().Extensions["pubkey"]
21+		if pubkey == "" {
22+			log.Error("pubkey not found in extensions", "subdomain", subdomain)
23 			return http.HandlerFunc(shared.UnauthorizedHandler)
24 		}
25 
26-		pubkeyStr := utils.KeyForKeyText(pubkey)
27-
28 		log = log.With(
29-			"pubkey", pubkeyStr,
30+			"pubkey", pubkey,
31 		)
32 
33 		props, err := shared.GetProjectFromSubdomain(subdomain)
34@@ -72,7 +69,7 @@ func createHttpHandler(apiConfig *shared.ApiConfig) CtxHttpBridge {
35 			return http.HandlerFunc(shared.UnauthorizedHandler)
36 		}
37 
38-		requester, _ := dbh.FindUserForKey("", pubkeyStr)
39+		requester, _ := dbh.FindUserForKey("", pubkey)
40 		if requester != nil {
41 			log = log.With(
42 				"requester", requester.Name,
43@@ -89,33 +86,18 @@ func createHttpHandler(apiConfig *shared.ApiConfig) CtxHttpBridge {
44 			requester, _ = dbh.FindUserForName(asUser)
45 		}
46 
47-		shared.SetUser(ctx, requester)
48-
49-		if !HasProjectAccess(project, owner, requester, pubkey) {
50+		ctx.Permissions().Extensions["user_id"] = requester.ID
51+		publicKey, err := ssh.ParsePublicKey([]byte(pubkey))
52+		if err != nil {
53+			return http.HandlerFunc(shared.UnauthorizedHandler)
54+		}
55+		if !HasProjectAccess(project, owner, requester, publicKey) {
56 			log.Error("no access")
57 			return http.HandlerFunc(shared.UnauthorizedHandler)
58 		}
59 
60 		log.Info("user has access to site")
61 
62-		/* routes := []shared.Route{
63-			// special API endpoint for tunnel users accessing site
64-			shared.NewCorsRoute("GET", "/api/current_user", func(w http.ResponseWriter, r *http.Request) {
65-				w.Header().Set("Content-Type", "application/json")
66-				user, err := shared.GetUser(ctx)
67-				if err != nil {
68-					logger.Error("could not find user", "err", err.Error())
69-					shared.JSONError(w, err.Error(), http.StatusNotFound)
70-					return
71-				}
72-				pico := shared.NewUserApi(user, pubkey)
73-				err = json.NewEncoder(w).Encode(pico)
74-				if err != nil {
75-					log.Error(err.Error())
76-				}
77-			}),
78-		} */
79-
80 		routes := NewWebRouter(
81 			apiConfig.Cfg,
82 			logger,
pgs/uploader.go link
+6 -10
 1diff --git a/pgs/uploader.go b/pgs/uploader.go
 2index 44f9aa4..f32e523 100644
 3--- a/pgs/uploader.go
 4+++ b/pgs/uploader.go
 5@@ -121,7 +121,7 @@ func (h *UploadAssetHandler) GetLogger() *slog.Logger {
 6 }
 7 
 8 func (h *UploadAssetHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReaderAtCloser, error) {
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return nil, nil, err
13 	}
14@@ -155,7 +155,7 @@ func (h *UploadAssetHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os
15 func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
16 	var fileList []os.FileInfo
17 
18-	user, err := shared.GetUser(s.Context())
19+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
20 	if err != nil {
21 		return fileList, err
22 	}
23@@ -197,7 +197,7 @@ func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool, recur
24 }
25 
26 func (h *UploadAssetHandler) Validate(s ssh.Session) error {
27-	user, err := shared.GetUser(s.Context())
28+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
29 	if err != nil {
30 		return err
31 	}
32@@ -248,7 +248,7 @@ func (h *UploadAssetHandler) findDenylist(bucket sst.Bucket, project *db.Project
33 }
34 
35 func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
36-	user, err := shared.GetUser(s.Context())
37+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
38 	if user == nil || err != nil {
39 		h.Cfg.Logger.Error("user not found in ctx", "err", err.Error())
40 		return "", err
41@@ -314,11 +314,7 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (s
42 		return "", err
43 	}
44 
45-	featureFlag, err := shared.GetFeatureFlag(s.Context())
46-	if err != nil {
47-		return "", err
48-	}
49-
50+	featureFlag := shared.FindPlusFF(h.DBPool, h.Cfg, user.ID)
51 	// calculate the filsize difference between the same file already
52 	// stored and the updated file being uploaded
53 	assetFilename := shared.GetAssetFileName(entry)
54@@ -424,7 +420,7 @@ func isSpecialFile(entry *sendutils.FileEntry) bool {
55 }
56 
57 func (h *UploadAssetHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
58-	user, err := shared.GetUser(s.Context())
59+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
60 	if err != nil {
61 		h.Cfg.Logger.Error("user not found in ctx", "err", err.Error())
62 		return err
pico/file_handler.go link
+4 -4
 1diff --git a/pico/file_handler.go b/pico/file_handler.go
 2index 15a1d65..1e61d90 100644
 3--- a/pico/file_handler.go
 4+++ b/pico/file_handler.go
 5@@ -56,7 +56,7 @@ func (h *UploadHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error
 6 }
 7 
 8 func (h *UploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReaderAtCloser, error) {
 9-	user, err := shared.GetUser(s.Context())
10+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
11 	if err != nil {
12 		return nil, nil, err
13 	}
14@@ -80,7 +80,7 @@ func (h *UploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.File
15 
16 func (h *UploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
17 	var fileList []os.FileInfo
18-	user, err := shared.GetUser(s.Context())
19+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
20 	if err != nil {
21 		return fileList, err
22 	}
23@@ -135,7 +135,7 @@ func (h *UploadHandler) Validate(s ssh.Session) error {
24 		return fmt.Errorf("must have username set")
25 	}
26 
27-	shared.SetUser(s.Context(), user)
28+	s.Permissions().Extensions["user_id"] = user.ID
29 	return nil
30 }
31 
32@@ -276,7 +276,7 @@ func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger,
33 
34 func (h *UploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
35 	logger := h.Cfg.Logger
36-	user, err := shared.GetUser(s.Context())
37+	user, err := h.DBPool.FindUser(s.Permissions().Extensions["user_id"])
38 	if err != nil {
39 		logger.Error(err.Error())
40 		return "", err
pico/ssh.go link
+0 -1
 1diff --git a/pico/ssh.go b/pico/ssh.go
 2index 6288832..8f24e81 100644
 3--- a/pico/ssh.go
 4+++ b/pico/ssh.go
 5@@ -28,7 +28,6 @@ import (
 6 )
 7 
 8 func authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 9-	shared.SetPublicKey(ctx, key)
10 	return true
11 }
12 
pipe/cli.go link
+1 -1
 1diff --git a/pipe/cli.go b/pipe/cli.go
 2index 94a6f72..b71a251 100644
 3--- a/pipe/cli.go
 4+++ b/pipe/cli.go
 5@@ -141,7 +141,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
 6 			logger := handler.Logger
 7 			ctx := sesh.Context()
 8 
 9-			user, err := shared.GetUser(ctx)
10+			user, err := handler.DBPool.FindUser(sesh.Permissions().Extensions["user_id"])
11 			if err != nil {
12 				logger.Info("user not found", "err", err)
13 			}
shared/ssh.go link
+24 -63
  1diff --git a/shared/ssh.go b/shared/ssh.go
  2index b30a2ef..f0270e7 100644
  3--- a/shared/ssh.go
  4+++ b/shared/ssh.go
  5@@ -1,7 +1,6 @@
  6 package shared
  7 
  8 import (
  9-	"fmt"
 10 	"log/slog"
 11 
 12 	"github.com/charmbracelet/ssh"
 13@@ -9,47 +8,6 @@ import (
 14 	"github.com/picosh/utils"
 15 )
 16 
 17-type ctxUserKey struct{}
 18-type ctxFeatureFlagKey struct{}
 19-
 20-func GetUser(ctx ssh.Context) (*db.User, error) {
 21-	user, ok := ctx.Value(ctxUserKey{}).(*db.User)
 22-	if !ok {
 23-		return user, fmt.Errorf("user not set on `ssh.Context()` for connection")
 24-	}
 25-	return user, nil
 26-}
 27-
 28-func SetUser(ctx ssh.Context, user *db.User) {
 29-	ctx.SetValue(ctxUserKey{}, user)
 30-}
 31-
 32-func GetFeatureFlag(ctx ssh.Context) (*db.FeatureFlag, error) {
 33-	ff, ok := ctx.Value(ctxFeatureFlagKey{}).(*db.FeatureFlag)
 34-	if !ok || ff.Name == "" {
 35-		return ff, fmt.Errorf("feature flag not set on `ssh.Context()` for connection")
 36-	}
 37-	return ff, nil
 38-}
 39-
 40-func SetFeatureFlag(ctx ssh.Context, ff *db.FeatureFlag) {
 41-	ctx.SetValue(ctxFeatureFlagKey{}, ff)
 42-}
 43-
 44-type ctxPublicKey struct{}
 45-
 46-func GetPublicKey(ctx ssh.Context) (ssh.PublicKey, error) {
 47-	pk, ok := ctx.Value(ctxPublicKey{}).(ssh.PublicKey)
 48-	if !ok {
 49-		return nil, fmt.Errorf("public key not set on `ssh.Context()` for connection")
 50-	}
 51-	return pk, nil
 52-}
 53-
 54-func SetPublicKey(ctx ssh.Context, pk ssh.PublicKey) {
 55-	ctx.SetValue(ctxPublicKey{}, pk)
 56-}
 57-
 58 type SshAuthHandler struct {
 59 	DBPool db.DB
 60 	Logger *slog.Logger
 61@@ -64,11 +22,28 @@ func NewSshAuthHandler(dbpool db.DB, logger *slog.Logger, cfg *ConfigSite) *SshA
 62 	}
 63 }
 64 
 65-func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 66-	SetPublicKey(ctx, key)
 67+func FindPlusFF(dbpool db.DB, cfg *ConfigSite, userID string) *db.FeatureFlag {
 68+	ff, _ := dbpool.FindFeatureForUser(userID, "plus")
 69+	// we have free tiers so users might not have a feature flag
 70+	// in which case we set sane defaults
 71+	if ff == nil {
 72+		ff = db.NewFeatureFlag(
 73+			userID,
 74+			"plus",
 75+			cfg.MaxSize,
 76+			cfg.MaxAssetSize,
 77+			cfg.MaxSpecialFileSize,
 78+		)
 79+	}
 80+	// this is jank
 81+	ff.Data.StorageMax = ff.FindStorageMax(cfg.MaxSize)
 82+	ff.Data.FileMax = ff.FindFileMax(cfg.MaxAssetSize)
 83+	ff.Data.SpecialFileMax = ff.FindSpecialFileMax(cfg.MaxSpecialFileSize)
 84+	return ff
 85+}
 86 
 87+func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 88 	pubkey := utils.KeyForKeyText(key)
 89-
 90 	user, err := r.DBPool.FindUserForKey(ctx.User(), pubkey)
 91 	if err != nil {
 92 		r.Logger.Error(
 93@@ -84,24 +59,10 @@ func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) b
 94 		return false
 95 	}
 96 
 97-	ff, _ := r.DBPool.FindFeatureForUser(user.ID, "plus")
 98-	// we have free tiers so users might not have a feature flag
 99-	// in which case we set sane defaults
100-	if ff == nil {
101-		ff = db.NewFeatureFlag(
102-			user.ID,
103-			"plus",
104-			r.Cfg.MaxSize,
105-			r.Cfg.MaxAssetSize,
106-			r.Cfg.MaxSpecialFileSize,
107-		)
108+	if ctx.Permissions().Extensions == nil {
109+		ctx.Permissions().Extensions = map[string]string{}
110 	}
111-	// this is jank
112-	ff.Data.StorageMax = ff.FindStorageMax(r.Cfg.MaxSize)
113-	ff.Data.FileMax = ff.FindFileMax(r.Cfg.MaxAssetSize)
114-	ff.Data.SpecialFileMax = ff.FindSpecialFileMax(r.Cfg.MaxSpecialFileSize)
115-
116-	SetUser(ctx, user)
117-	SetFeatureFlag(ctx, ff)
118+	ctx.Permissions().Extensions["user_id"] = user.ID
119+	ctx.Permissions().Extensions["pubkey"] = pubkey
120 	return true
121 }