- commit
- 2f0f0ee
- parent
- 31dbc6e
- author
- Eric Bower
- date
- 2024-03-01 02:15:16 +0000 UTC
refactor(shared): http router
+7,
-5
1@@ -3,9 +3,7 @@ package feeds
2 import (
3 "fmt"
4 "net/http"
5- "time"
6
7- gocache "github.com/patrickmn/go-cache"
8 "github.com/picosh/pico/db/postgres"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/pico/shared/storage"
11@@ -50,8 +48,6 @@ func StartApiServer() {
12 st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
13 }
14
15- cache := gocache.New(2*time.Minute, 5*time.Minute)
16-
17 if err != nil {
18 logger.Error(err.Error())
19 }
20@@ -68,7 +64,13 @@ func StartApiServer() {
21
22 mainRoutes := createMainRoutes(staticRoutes)
23
24- handler := shared.CreateServe(mainRoutes, []shared.Route{}, cfg, db, st, logger, cache)
25+ httpCtx := &shared.HttpCtx{
26+ Cfg: cfg,
27+ Dbpool: db,
28+ Storage: st,
29+ Logger: logger,
30+ }
31+ handler := shared.CreateServe(mainRoutes, []shared.Route{}, httpCtx)
32 router := http.HandlerFunc(handler)
33
34 portStr := fmt.Sprintf(":%s", cfg.Port)
M
go.mod
+0,
-1
1@@ -28,7 +28,6 @@ require (
2 github.com/muesli/reflow v0.3.0
3 github.com/muesli/termenv v0.15.2
4 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
5- github.com/patrickmn/go-cache v2.1.0+incompatible
6 github.com/picosh/pobj v0.0.0-20240218150308-1dc70e819bbf
7 github.com/picosh/ptun v0.0.0-20240225010823-a5e18b5be928
8 github.com/picosh/send v0.0.0-20240217194807-77b972121e63
M
go.sum
+0,
-2
1@@ -183,8 +183,6 @@ github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577 h1:hVmVNt
2 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577/go.mod h1:G3Cu1AW+dmRLDFpOi8eUAfc3cGoRHUjTkGjeRcndgl4=
3 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e h1:76Dng5ms0fR+26doKZAvNqhi2UPfnLxGfPIDEr+BBlM=
4 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e/go.mod h1:nZBDA7+RD63GDJwjZmxhxac65MJqiCIHUUUvdYOsFkk=
5-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
6-github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
7 github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
8 github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
9 github.com/picosh/pobj v0.0.0-20240218150308-1dc70e819bbf h1:xtr5DoSgoOqbPFnm8p5YjTqrRKVW8KYI0bDwwRiG8yI=
+7,
-7
1@@ -12,7 +12,6 @@ import (
2 _ "net/http/pprof"
3
4 "github.com/gorilla/feeds"
5- gocache "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/db/postgres"
8 "github.com/picosh/pico/pgs"
9@@ -311,11 +310,6 @@ func StartApiServer() {
10 st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 }
12
13- // cache resizing images since they are CPU-bound
14- // we want to clear the cache since we are storing images
15- // as []byte in-memory
16- cache := gocache.New(2*time.Minute, 5*time.Minute)
17-
18 if err != nil {
19 logger.Error(err.Error())
20 }
21@@ -328,7 +322,13 @@ func StartApiServer() {
22 mainRoutes := createMainRoutes(staticRoutes)
23 subdomainRoutes := createSubdomainRoutes(staticRoutes)
24
25- handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
26+ httpCtx := &shared.HttpCtx{
27+ Cfg: cfg,
28+ Dbpool: db,
29+ Storage: st,
30+ Logger: logger,
31+ }
32+ handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
33 router := http.HandlerFunc(handler)
34
35 portStr := fmt.Sprintf(":%s", cfg.Port)
+7,
-4
1@@ -8,7 +8,6 @@ import (
2 "os"
3 "time"
4
5- gocache "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/db/postgres"
8 "github.com/picosh/pico/shared"
9@@ -364,8 +363,6 @@ func StartApiServer() {
10 st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 }
12
13- cache := gocache.New(2*time.Minute, 5*time.Minute)
14-
15 if err != nil {
16 logger.Error(err.Error())
17 return
18@@ -382,7 +379,13 @@ func StartApiServer() {
19 mainRoutes := createMainRoutes(staticRoutes)
20 subdomainRoutes := createSubdomainRoutes(staticRoutes)
21
22- handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
23+ httpCtx := &shared.HttpCtx{
24+ Cfg: cfg,
25+ Dbpool: db,
26+ Storage: st,
27+ Logger: logger,
28+ }
29+ handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
30 router := http.HandlerFunc(handler)
31
32 portStr := fmt.Sprintf(":%s", cfg.Port)
+7,
-10
1@@ -15,7 +15,6 @@ import (
2 _ "net/http/pprof"
3
4 "github.com/gorilla/feeds"
5- gocache "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/db/postgres"
8 "github.com/picosh/pico/shared"
9@@ -33,7 +32,6 @@ type AssetHandler struct {
10 Dbpool db.DB
11 Storage storage.StorageServe
12 Logger *slog.Logger
13- Cache *gocache.Cache
14 UserID string
15 Bucket sst.Bucket
16 ImgProcessOpts *storage.ImgProcessOpts
17@@ -352,7 +350,6 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, hasPe
18 dbpool := shared.GetDB(r)
19 st := shared.GetStorage(r)
20 logger := shared.GetLogger(r)
21- cache := shared.GetCache(r)
22
23 props, err := getProjectFromSubdomain(subdomain)
24 if err != nil {
25@@ -410,7 +407,6 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, hasPe
26 Dbpool: dbpool,
27 Storage: st,
28 Logger: logger,
29- Cache: cache,
30 Bucket: bucket,
31 ImgProcessOpts: opts,
32 }
33@@ -487,17 +483,18 @@ func StartApiServer() {
34 st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
35 }
36
37- // cache resizing images since they are CPU-bound
38- // we want to clear the cache since we are storing images
39- // as []byte in-memory
40- cache := gocache.New(2*time.Minute, 5*time.Minute)
41-
42 if err != nil {
43 logger.Error(err.Error())
44 return
45 }
46
47- handler := shared.CreateServe(mainRoutes, createSubdomainRoutes(publicPerm), cfg, db, st, logger, cache)
48+ httpCtx := &shared.HttpCtx{
49+ Cfg: cfg,
50+ Dbpool: db,
51+ Storage: st,
52+ Logger: logger,
53+ }
54+ handler := shared.CreateServe(mainRoutes, createSubdomainRoutes(publicPerm), httpCtx)
55 router := http.HandlerFunc(handler)
56
57 portStr := fmt.Sprintf(":%s", cfg.Port)
+97,
-92
1@@ -14,7 +14,6 @@ import (
2 "github.com/charmbracelet/ssh"
3 "github.com/charmbracelet/wish"
4 bm "github.com/charmbracelet/wish/bubbletea"
5- gocache "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/db/postgres"
8 uploadassets "github.com/picosh/pico/filehandlers/assets"
9@@ -89,6 +88,95 @@ type PicoApi struct {
10 PublicKey string `json:"public_key"`
11 }
12
13+type CtxHttpBridge = func(ssh.Context) http.Handler
14+
15+func createHttpHandler(httpCtx *shared.HttpCtx) CtxHttpBridge {
16+ return func(ctx ssh.Context) http.Handler {
17+ subdomain := ctx.User()
18+ dbh := httpCtx.Dbpool
19+ logger := httpCtx.Logger
20+ log := logger.With(
21+ "subdomain", subdomain,
22+ )
23+
24+ pubkey, err := getPublicKeyCtx(ctx)
25+ if err != nil {
26+ log.Error(err.Error(), "subdomain", subdomain)
27+ return http.HandlerFunc(unauthorizedHandler)
28+ }
29+ pubkeyStr, err := shared.KeyForKeyText(pubkey)
30+ if err != nil {
31+ log.Error(err.Error())
32+ return http.HandlerFunc(unauthorizedHandler)
33+ }
34+ log = log.With(
35+ "pubkey", pubkeyStr,
36+ )
37+
38+ props, err := getProjectFromSubdomain(subdomain)
39+ if err != nil {
40+ log.Error(err.Error())
41+ return http.HandlerFunc(unauthorizedHandler)
42+ }
43+
44+ owner, err := dbh.FindUserForName(props.Username)
45+ if err != nil {
46+ log.Error(err.Error())
47+ return http.HandlerFunc(unauthorizedHandler)
48+ }
49+ log = log.With(
50+ "owner", owner.Name,
51+ )
52+
53+ project, err := dbh.FindProjectByName(owner.ID, props.ProjectName)
54+ if err != nil {
55+ log.Error(err.Error())
56+ return http.HandlerFunc(unauthorizedHandler)
57+ }
58+
59+ requester, _ := dbh.FindUserForKey("", pubkeyStr)
60+ if requester != nil {
61+ log = logger.With(
62+ "requester", requester.Name,
63+ )
64+ }
65+
66+ if !HasProjectAccess(project, owner, requester, pubkey) {
67+ log.Error("no access")
68+ return http.HandlerFunc(unauthorizedHandler)
69+ }
70+
71+ log.Info("user has access to site")
72+
73+ routes := []shared.Route{
74+ // special API endpoint for tunnel users accessing site
75+ shared.NewRoute("GET", "/pico", func(w http.ResponseWriter, r *http.Request) {
76+ w.Header().Set("Content-Type", "application/json")
77+ pico := &PicoApi{
78+ UserID: "",
79+ UserName: "",
80+ PublicKey: pubkeyStr,
81+ }
82+ if requester != nil {
83+ pico.UserID = requester.ID
84+ pico.UserName = requester.Name
85+ }
86+ err := json.NewEncoder(w).Encode(pico)
87+ if err != nil {
88+ log.Error(err.Error())
89+ }
90+ }),
91+ }
92+
93+ subdomainRoutes := createSubdomainRoutes(allowPerm)
94+ routes = append(routes, subdomainRoutes...)
95+ finctx := httpCtx.CreateCtx(ctx, subdomain)
96+ httpHandler := shared.CreateServeBasic(routes, finctx)
97+ httpRouter := http.HandlerFunc(httpHandler)
98+ return httpRouter
99+ }
100+}
101+
102 func StartSshServer() {
103 host := shared.GetEnv("PGS_HOST", "0.0.0.0")
104 port := shared.GetEnv("PGS_SSH_PORT", "2222")
105@@ -116,99 +204,16 @@ func StartSshServer() {
106 cfg,
107 st,
108 )
109- cache := gocache.New(2*time.Minute, 5*time.Minute)
110
111- webTunnel := &ptun.WebTunnelHandler{
112- Logger: logger,
113- HttpHandler: func(ctx ssh.Context) http.Handler {
114- subdomain := ctx.User()
115- log := logger.With(
116- "subdomain", subdomain,
117- )
118-
119- pubkey, err := getPublicKeyCtx(ctx)
120- if err != nil {
121- log.Error(err.Error(), "subdomain", subdomain)
122- return http.HandlerFunc(unauthorizedHandler)
123- }
124- pubkeyStr, err := shared.KeyForKeyText(pubkey)
125- if err != nil {
126- log.Error(err.Error())
127- return http.HandlerFunc(unauthorizedHandler)
128- }
129- log = log.With(
130- "pubkey", pubkeyStr,
131- )
132-
133- props, err := getProjectFromSubdomain(subdomain)
134- if err != nil {
135- log.Error(err.Error())
136- return http.HandlerFunc(unauthorizedHandler)
137- }
138-
139- owner, err := dbh.FindUserForName(props.Username)
140- if err != nil {
141- log.Error(err.Error())
142- return http.HandlerFunc(unauthorizedHandler)
143- }
144- log = log.With(
145- "owner", owner.Name,
146- )
147+ httpCtx := &shared.HttpCtx{
148+ Cfg: cfg,
149+ Dbpool: dbh,
150+ Storage: st,
151+ }
152
153- project, err := dbh.FindProjectByName(owner.ID, props.ProjectName)
154- if err != nil {
155- log.Error(err.Error())
156- return http.HandlerFunc(unauthorizedHandler)
157- }
158-
159- requester, _ := dbh.FindUserForKey("", pubkeyStr)
160- if requester != nil {
161- log = logger.With(
162- "requester", requester.Name,
163- )
164- }
165-
166- if !HasProjectAccess(project, owner, requester, pubkey) {
167- log.Error("no access")
168- return http.HandlerFunc(unauthorizedHandler)
169- }
170-
171- log.Info("user has access to site")
172-
173- routes := []shared.Route{
174- // special API endpoint for tunnel users accessing site
175- shared.NewRoute("GET", "/pico", func(w http.ResponseWriter, r *http.Request) {
176- w.Header().Set("Content-Type", "application/json")
177- pico := &PicoApi{
178- UserID: "",
179- UserName: "",
180- PublicKey: pubkeyStr,
181- }
182- if requester != nil {
183- pico.UserID = requester.ID
184- pico.UserName = requester.Name
185- }
186- err := json.NewEncoder(w).Encode(pico)
187- if err != nil {
188- log.Error(err.Error())
189- }
190- }),
191- }
192-
193- subdomainRoutes := createSubdomainRoutes(allowPerm)
194- routes = append(routes, subdomainRoutes...)
195- httpHandler := shared.CreateServeBasic(
196- routes,
197- subdomain,
198- cfg,
199- dbh,
200- st,
201- logger,
202- cache,
203- )
204- httpRouter := http.HandlerFunc(httpHandler)
205- return httpRouter
206- },
207+ webTunnel := &ptun.WebTunnelHandler{
208+ Logger: logger,
209+ HttpHandler: createHttpHandler(httpCtx),
210 }
211
212 s, err := wish.NewServer(
+7,
-4
1@@ -14,7 +14,6 @@ import (
2 "slices"
3
4 "github.com/gorilla/feeds"
5- gocache "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/db/postgres"
8 "github.com/picosh/pico/imgs"
9@@ -879,8 +878,6 @@ func StartApiServer() {
10 st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 }
12
13- cache := gocache.New(2*time.Minute, 5*time.Minute)
14-
15 if err != nil {
16 logger.Error(err.Error())
17 }
18@@ -894,7 +891,13 @@ func StartApiServer() {
19 mainRoutes := createMainRoutes(staticRoutes)
20 subdomainRoutes := createSubdomainRoutes(staticRoutes)
21
22- handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
23+ httpCtx := &shared.HttpCtx{
24+ Cfg: cfg,
25+ Dbpool: db,
26+ Storage: st,
27+ Logger: logger,
28+ }
29+ handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
30 router := http.HandlerFunc(handler)
31
32 portStr := fmt.Sprintf(":%s", cfg.Port)
1@@ -10,7 +10,6 @@ import (
2 "regexp"
3 "strings"
4
5- "github.com/patrickmn/go-cache"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/shared/storage"
8 )
9@@ -45,17 +44,25 @@ func CreatePProfRoutes(routes []Route) []Route {
10 }
11
12 type ServeFn func(http.ResponseWriter, *http.Request)
13+type HttpCtx struct {
14+ Logger *slog.Logger
15+ Cfg *ConfigSite
16+ Dbpool db.DB
17+ Storage storage.StorageServe
18+}
19+
20+func (hc *HttpCtx) CreateCtx(prevCtx context.Context, subdomain string) context.Context {
21+ loggerCtx := context.WithValue(prevCtx, ctxLoggerKey{}, hc.Logger)
22+ subdomainCtx := context.WithValue(loggerCtx, ctxSubdomainKey{}, subdomain)
23+ dbCtx := context.WithValue(subdomainCtx, ctxDBKey{}, hc.Dbpool)
24+ storageCtx := context.WithValue(dbCtx, ctxStorageKey{}, hc.Storage)
25+ cfgCtx := context.WithValue(storageCtx, ctxCfg{}, hc.Cfg)
26+ return cfgCtx
27+}
28
29-func CreateServeBasic(routes []Route, subdomain string, cfg *ConfigSite, dbpool db.DB, st storage.StorageServe, logger *slog.Logger, cache *cache.Cache) ServeFn {
30+func CreateServeBasic(routes []Route, ctx context.Context) ServeFn {
31 return func(w http.ResponseWriter, r *http.Request) {
32 var allow []string
33- loggerCtx := context.WithValue(r.Context(), ctxLoggerKey{}, logger)
34- subdomainCtx := context.WithValue(loggerCtx, ctxSubdomainKey{}, subdomain)
35- dbCtx := context.WithValue(subdomainCtx, ctxDBKey{}, dbpool)
36- storageCtx := context.WithValue(dbCtx, ctxStorageKey{}, st)
37- cfgCtx := context.WithValue(storageCtx, ctxCfg{}, cfg)
38- cacheCtx := context.WithValue(cfgCtx, ctxCacheKey{}, cache)
39-
40 for _, route := range routes {
41 matches := route.Regex.FindStringSubmatch(r.URL.Path)
42 if len(matches) > 0 {
43@@ -63,8 +70,8 @@ func CreateServeBasic(routes []Route, subdomain string, cfg *ConfigSite, dbpool
44 allow = append(allow, route.Method)
45 continue
46 }
47- ctx := context.WithValue(cacheCtx, ctxKey{}, matches[1:])
48- route.Handler(w, r.WithContext(ctx))
49+ finctx := context.WithValue(ctx, ctxKey{}, matches[1:])
50+ route.Handler(w, r.WithContext(finctx))
51 return
52 }
53 }
54@@ -103,10 +110,11 @@ func findRouteConfig(r *http.Request, routes []Route, subdomainRoutes []Route, c
55 return curRoutes, subdomain
56 }
57
58-func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, st storage.StorageServe, logger *slog.Logger, cache *cache.Cache) ServeFn {
59+func CreateServe(routes []Route, subdomainRoutes []Route, httpCtx *HttpCtx) ServeFn {
60 return func(w http.ResponseWriter, r *http.Request) {
61- curRoutes, subdomain := findRouteConfig(r, routes, subdomainRoutes, cfg)
62- router := CreateServeBasic(curRoutes, subdomain, cfg, dbpool, st, logger, cache)
63+ curRoutes, subdomain := findRouteConfig(r, routes, subdomainRoutes, httpCtx.Cfg)
64+ ctx := httpCtx.CreateCtx(r.Context(), subdomain)
65+ router := CreateServeBasic(curRoutes, ctx)
66 router(w, r)
67 }
68 }
69@@ -115,7 +123,6 @@ type ctxDBKey struct{}
70 type ctxStorageKey struct{}
71 type ctxKey struct{}
72 type ctxLoggerKey struct{}
73-type ctxCacheKey struct{}
74 type ctxSubdomainKey struct{}
75 type ctxCfg struct{}
76
77@@ -127,10 +134,6 @@ func GetLogger(r *http.Request) *slog.Logger {
78 return r.Context().Value(ctxLoggerKey{}).(*slog.Logger)
79 }
80
81-func GetCache(r *http.Request) *cache.Cache {
82- return r.Context().Value(ctxCacheKey{}).(*cache.Cache)
83-}
84-
85 func GetDB(r *http.Request) db.DB {
86 return r.Context().Value(ctxDBKey{}).(db.DB)
87 }