+9
-4
app/app.go
+9
-4
app/app.go
···
20
)
21
22
type AppConfig struct {
23
+
Entrypoint string `json:"entrypoint,omitempty"`
24
+
Root string `json:"root,omitempty"`
25
+
Crons []CronJob `json:"cron"`
26
+
Private bool `json:"private"`
27
+
PrivateRoutes []string `json:"privateRoutes"`
28
+
PublicRoutes []string `json:"publicRoutes"`
29
}
30
31
type CronJob struct {
32
+
Description string `json:"description"`
33
+
Schedule string `json:"schedule"`
34
+
Args []string `json:"args"`
35
}
36
37
type App struct {
+14
-4
cmd/crons.go
+14
-4
cmd/crons.go
···
47
Short: "List cron jobs",
48
RunE: func(cmd *cobra.Command, args []string) error {
49
var crons []CronItem
50
-
for _, appname := range k.MapKeys("apps") {
51
if len(args) > 0 && appname != args[0] {
52
continue
53
} else if len(args) == 0 && !flags.all {
···
69
}
70
}
71
72
-
for _, job := range k.Slices(fmt.Sprintf("apps.%s.crons", appname)) {
73
crons = append(crons, CronItem{
74
App: appname,
75
-
Args: job.Strings("args"),
76
-
Schedule: job.String("schedule"),
77
})
78
}
79
}
···
47
Short: "List cron jobs",
48
RunE: func(cmd *cobra.Command, args []string) error {
49
var crons []CronItem
50
+
apps, err := app.ListApps(k.String("dir"))
51
+
if err != nil {
52
+
return fmt.Errorf("failed to list apps: %w", err)
53
+
}
54
+
55
+
for _, appname := range apps {
56
if len(args) > 0 && appname != args[0] {
57
continue
58
} else if len(args) == 0 && !flags.all {
···
74
}
75
}
76
77
+
a, err := app.LoadApp(appname, k.String("dir"), k.String("domain"), k.Bool(fmt.Sprintf("apps.%s.admin", appname)))
78
+
if err != nil {
79
+
return fmt.Errorf("failed to load app %s: %w", appname, err)
80
+
}
81
+
82
+
for _, job := range a.Config.Crons {
83
crons = append(crons, CronItem{
84
App: appname,
85
+
Args: job.Args,
86
+
Schedule: job.Schedule,
87
})
88
}
89
}
+19
-19
cmd/up.go
+19
-19
cmd/up.go
···
694
return
695
}
696
697
claims, err := me.extractClaims(r)
698
-
if err != nil && isRoutePrivate(appname, r.URL.Path) {
699
if me.oidcIssuerUrl == nil {
700
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
701
return
···
775
}
776
}
777
778
-
if isRoutePrivate(appname, r.URL.Path) && !isAuthorized(appname, claims.Email, claims.Group) {
779
if claims.Email == "" {
780
http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin", r.Host), http.StatusTemporaryRedirect)
781
return
···
790
r.Header.Set("Remote-Group", claims.Group)
791
r.Header.Set("Remote-Name", claims.Name)
792
793
-
wk, err := me.GetWorker(appname, k.String("dir"), k.String("domain"))
794
-
if err != nil {
795
-
if errors.Is(err, app.ErrAppNotFound) {
796
-
w.WriteHeader(http.StatusNotFound)
797
-
w.Write([]byte(fmt.Sprintf("No app found for host %s", r.Host)))
798
-
return
799
-
}
800
-
801
-
w.WriteHeader(http.StatusInternalServerError)
802
-
fmt.Fprintf(w, "failed to get worker: %v", err)
803
-
return
804
-
}
805
-
806
wk.ServeHTTP(w, r)
807
}
808
809
-
func isRoutePrivate(appname string, route string) bool {
810
-
isPrivate := k.Bool(fmt.Sprintf("apps.%s.private", appname))
811
812
-
for _, publicRoute := range k.Strings(fmt.Sprintf("apps.%s.publicRoutes", appname)) {
813
if ok, _ := doublestar.Match(publicRoute, route); ok {
814
isPrivate = false
815
}
816
}
817
818
-
for _, privateRoute := range k.Strings(fmt.Sprintf("apps.%s.privateRoutes", appname)) {
819
if ok, _ := doublestar.Match(privateRoute, route); ok {
820
isPrivate = true
821
}
···
694
return
695
}
696
697
+
wk, err := me.GetWorker(appname, k.String("dir"), k.String("domain"))
698
+
if err != nil {
699
+
if errors.Is(err, app.ErrAppNotFound) {
700
+
w.WriteHeader(http.StatusNotFound)
701
+
w.Write([]byte(fmt.Sprintf("No app found for host %s", r.Host)))
702
+
return
703
+
}
704
+
705
+
w.WriteHeader(http.StatusInternalServerError)
706
+
fmt.Fprintf(w, "failed to get worker: %v", err)
707
+
return
708
+
}
709
+
710
claims, err := me.extractClaims(r)
711
+
if err != nil && isRoutePrivate(wk.App, r.URL.Path) {
712
if me.oidcIssuerUrl == nil {
713
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
714
return
···
788
}
789
}
790
791
+
if isRoutePrivate(wk.App, r.URL.Path) && !isAuthorized(appname, claims.Email, claims.Group) {
792
if claims.Email == "" {
793
http.Redirect(w, r, fmt.Sprintf("https://%s/_smallweb/signin", r.Host), http.StatusTemporaryRedirect)
794
return
···
803
r.Header.Set("Remote-Group", claims.Group)
804
r.Header.Set("Remote-Name", claims.Name)
805
806
wk.ServeHTTP(w, r)
807
}
808
809
+
func isRoutePrivate(a app.App, route string) bool {
810
+
isPrivate := a.Config.Private
811
812
+
for _, publicRoute := range a.Config.PublicRoutes {
813
if ok, _ := doublestar.Match(publicRoute, route); ok {
814
isPrivate = false
815
}
816
}
817
818
+
for _, privateRoute := range a.Config.PrivateRoutes {
819
if ok, _ := doublestar.Match(privateRoute, route); ok {
820
isPrivate = true
821
}
-1
example/.smallweb/config.json
-1
example/.smallweb/config.json
+1
-4
example/ls/smallweb.json
+1
-4
example/ls/smallweb.json
-46
schemas/config.schema.json
-46
schemas/config.schema.json
···
65
"description": "Give the app admin privileges",
66
"type": "boolean"
67
},
68
-
"private": {
69
-
"description": "Protect all routes behind authentication",
70
-
"type": "boolean"
71
-
},
72
-
"privateRoutes": {
73
-
"description": "Make specific routes private",
74
-
"type": "array",
75
-
"items": {
76
-
"type": "string"
77
-
}
78
-
},
79
-
"publicRoutes": {
80
-
"description": "Make specific routes public",
81
-
"type": "array",
82
-
"items": {
83
-
"type": "string"
84
-
}
85
-
},
86
-
"crons": {
87
-
"description": "Cron jobs",
88
-
"type": "array",
89
-
"items": {
90
-
"type": "object",
91
-
"required": [
92
-
"schedule",
93
-
"args"
94
-
],
95
-
"properties": {
96
-
"schedule": {
97
-
"description": "Cron schedule",
98
-
"type": "string"
99
-
},
100
-
"description": {
101
-
"type": "string",
102
-
"description": "An optional description for the task"
103
-
},
104
-
"args": {
105
-
"description": "Cron arguments",
106
-
"type": "array",
107
-
"items": {
108
-
"type": "string"
109
-
}
110
-
}
111
-
}
112
-
}
113
-
},
114
"additionalDomains": {
115
"description": "Additional app domains",
116
"type": "array",
+46
schemas/manifest.schema.json
+46
schemas/manifest.schema.json
···
10
"description": "The root directory of the project",
11
"type": "string"
12
},
13
+
"private": {
14
+
"description": "Protect all routes behind authentication",
15
+
"type": "boolean"
16
+
},
17
+
"privateRoutes": {
18
+
"description": "Make specific routes private",
19
+
"type": "array",
20
+
"items": {
21
+
"type": "string"
22
+
}
23
+
},
24
+
"publicRoutes": {
25
+
"description": "Make specific routes public",
26
+
"type": "array",
27
+
"items": {
28
+
"type": "string"
29
+
}
30
+
},
31
+
"crons": {
32
+
"description": "Cron jobs",
33
+
"type": "array",
34
+
"items": {
35
+
"type": "object",
36
+
"required": [
37
+
"schedule",
38
+
"args"
39
+
],
40
+
"properties": {
41
+
"schedule": {
42
+
"description": "Cron schedule",
43
+
"type": "string"
44
+
},
45
+
"description": {
46
+
"type": "string",
47
+
"description": "An optional description for the task"
48
+
},
49
+
"args": {
50
+
"description": "Cron arguments",
51
+
"type": "array",
52
+
"items": {
53
+
"type": "string"
54
+
}
55
+
}
56
+
}
57
+
}
58
+
},
59
"labels": {
60
"description": "Labels for the project",
61
"type": "object",