this repo has no description smallweb.run
smallweb
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

add ability to set public and private routes

pomdtr d0ddf1ca bf067435

+71 -23
+27 -3
app/app.go
··· 9 9 "path/filepath" 10 10 "strings" 11 11 12 + "github.com/bmatcuk/doublestar/v4" 12 13 "github.com/getsops/sops/v3/decrypt" 13 14 "github.com/joho/godotenv" 14 15 "github.com/pomdtr/smallweb/utils" ··· 20 21 ) 21 22 22 23 type AppConfig struct { 23 - Entrypoint string `json:"entrypoint,omitempty"` 24 - Root string `json:"root,omitempty"` 25 - Crons []CronJob `json:"crons,omitempty"` 24 + Entrypoint string `json:"entrypoint,omitempty"` 25 + Root string `json:"root,omitempty"` 26 + Crons []CronJob `json:"crons,omitempty"` 27 + Private bool `json:"private,omitempty"` 28 + PrivateRoutes []string `json:"privateRoutes,omitempty"` 29 + PublicRoutes []string `json:"publicRoutes,omitempty"` 26 30 } 27 31 28 32 type CronJob struct { ··· 220 224 221 225 return "jsr:@smallweb/file-server@0.8.2" 222 226 } 227 + 228 + func (me App) IsRoutePrivate(route string) bool { 229 + routeIsPrivate := me.Config.Private 230 + for _, publicRoute := range me.Config.PublicRoutes { 231 + if isMatch, _ := doublestar.Match(publicRoute, route); isMatch { 232 + routeIsPrivate = false 233 + break 234 + } 235 + 236 + } 237 + 238 + for _, privateRoute := range me.Config.PrivateRoutes { 239 + if isMatch, _ := doublestar.Match(privateRoute, route); isMatch { 240 + routeIsPrivate = true 241 + break 242 + } 243 + } 244 + 245 + return routeIsPrivate 246 + }
+17 -16
cmd/up.go
··· 492 492 return 493 493 } 494 494 495 + a, err := app.LoadApp(appname, k.String("dir"), k.String("domain"), k.Bool(fmt.Sprintf("apps.%s.admin", appname))) 496 + if err != nil { 497 + w.WriteHeader(http.StatusInternalServerError) 498 + w.Write([]byte(fmt.Sprintf("failed to load app: %v", err))) 499 + return 500 + } 501 + 495 502 r.Header.Del("X-Smallweb-Email") 496 - isPrivate := k.Bool(fmt.Sprintf("apps.%s.private", appname)) 497 - if isPrivate { 503 + if a.IsRoutePrivate(r.URL.Path) { 498 504 if me.issuer == "" { 499 505 http.Error(w, "openauth issuer not set", http.StatusInternalServerError) 500 506 return ··· 663 669 664 670 accessTokenCookie, err := r.Cookie("access_token") 665 671 if err != nil { 666 - http.Redirect(w, r, fmt.Sprintf("https://%s/oauth/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 672 + http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 667 673 return 668 674 } 669 675 accessToken := accessTokenCookie.Value ··· 679 685 if err != nil && errors.Is(err, jwt.ErrTokenExpired) { 680 686 refreshTokenCookie, err := r.Cookie("refresh_token") 681 687 if err != nil { 682 - http.Redirect(w, r, fmt.Sprintf("https://%s/oauth/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 688 + http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 683 689 return 684 690 } 685 691 ··· 688 694 689 695 oauth2Token, err := tokenSource.Token() 690 696 if err != nil { 691 - http.Redirect(w, r, fmt.Sprintf("https://%s/oauth/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 697 + http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 692 698 return 693 699 } 694 700 ··· 714 720 715 721 token, err = jwt.ParseWithClaims(oauth2Token.AccessToken, &claims, me.keyfunc.Keyfunc, jwt.WithAudience(clientID)) 716 722 if err != nil { 717 - http.Redirect(w, r, fmt.Sprintf("https://%s/oauth/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 723 + http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 718 724 return 719 725 } 720 726 } else if err != nil { ··· 723 729 } 724 730 725 731 if !token.Valid { 726 - http.Redirect(w, r, fmt.Sprintf("https://%s/oauth/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 732 + http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin?success_url=%s", r.Host, r.URL.Path), http.StatusTemporaryRedirect) 727 733 return 728 734 } 729 735 ··· 738 744 r.Header.Set("X-Smallweb-Email", claims.Properties.Email) 739 745 } 740 746 741 - wk, err := me.GetWorker(appname, k.String("dir"), k.String("domain")) 747 + wk, err := me.GetWorker(a, k.String("dir"), k.String("domain")) 742 748 if err != nil { 743 749 if errors.Is(err, app.ErrAppNotFound) { 744 750 w.WriteHeader(http.StatusNotFound) ··· 782 788 return "", false, false 783 789 } 784 790 785 - func (me *Handler) GetWorker(appname, rootDir, domain string) (*worker.Worker, error) { 786 - if wk, ok := me.workers[appname]; ok && wk.IsRunning() && me.watcher.GetAppMtime(appname).Before(wk.StartedAt) { 791 + func (me *Handler) GetWorker(a app.App, rootDir, domain string) (*worker.Worker, error) { 792 + if wk, ok := me.workers[a.Name]; ok && wk.IsRunning() && me.watcher.GetAppMtime(a.Name).Before(wk.StartedAt) { 787 793 return wk, nil 788 794 } 789 795 790 796 me.mu.Lock() 791 797 defer me.mu.Unlock() 792 798 793 - a, err := app.LoadApp(appname, rootDir, domain, k.Bool(fmt.Sprintf("apps.%s.admin", appname))) 794 - if err != nil { 795 - return nil, fmt.Errorf("failed to load app: %w", err) 796 - } 797 - 798 799 wk := worker.NewWorker(a) 799 800 800 801 if err := wk.Start(); err != nil { 801 802 return nil, fmt.Errorf("failed to start worker: %w", err) 802 803 } 803 804 804 - me.workers[appname] = wk 805 + me.workers[a.Name] = wk 805 806 return wk, nil 806 807 }
+1
go.mod
··· 20 20 21 21 require ( 22 22 github.com/MicahParks/keyfunc/v3 v3.3.10 23 + github.com/bmatcuk/doublestar/v4 v4.8.1 23 24 github.com/caddyserver/certmagic v0.22.0 24 25 github.com/charmbracelet/log v0.4.1 25 26 github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef
+2
go.sum
··· 117 117 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 118 118 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 119 119 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 120 + github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= 121 + github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 120 122 github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw= 121 123 github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs= 122 124 github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
-4
schemas/config.schema.json
··· 55 55 "description": "Give the app admin privileges", 56 56 "type": "boolean" 57 57 }, 58 - "private": { 59 - "description": "Protect the app with authentication", 60 - "type": "boolean" 61 - }, 62 58 "additionalDomains": { 63 59 "description": "Additional app domains", 64 60 "type": "array",
+18
schemas/manifest.schema.json
··· 10 10 "description": "The root directory of the project", 11 11 "type": "string" 12 12 }, 13 + "private": { 14 + "type": "boolean", 15 + "description": "Whether the app is private" 16 + }, 17 + "privateRoutes": { 18 + "type": "array", 19 + "description": "An array of private routes", 20 + "items": { 21 + "type": "string" 22 + } 23 + }, 24 + "publicRoutes": { 25 + "type": "array", 26 + "description": "An array of public routes", 27 + "items": { 28 + "type": "string" 29 + } 30 + }, 13 31 "crons": { 14 32 "description": "An array of cron jobs", 15 33 "type": "array",
+6
workspace/ls/smallweb.json
··· 1 + { 2 + "private": true, 3 + "publicRoutes": [ 4 + "/public/*" 5 + ] 6 + }