From 9bea6805d53b2158ee246a70fa27f4c70022013e Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Sun, 2 Jun 2024 12:23:22 +0100 Subject: [PATCH] Update to make custom themes easier --- {theme => pages/assets}/style.css | 0 pages/manage-apps-create.go.html | 68 ++++++++++ pages/manage-apps-edit.go.html | 75 +++++++++++ pages/manage-apps.go.html | 151 +++++++--------------- pages/manage-users-create.go.html | 49 +++++++ pages/manage-users-edit.go.html | 51 ++++++++ pages/manage-users.go.html | 204 ++++++++++-------------------- pages/pages.go | 33 ++++- server/auth_test.go | 4 +- server/manage-apps.go | 27 +++- server/manage-users.go | 28 +++- server/server.go | 17 ++- theme/theme.go | 6 - utils/once.go | 15 +++ 14 files changed, 466 insertions(+), 262 deletions(-) rename {theme => pages/assets}/style.css (100%) create mode 100644 pages/manage-apps-create.go.html create mode 100644 pages/manage-apps-edit.go.html create mode 100644 pages/manage-users-create.go.html create mode 100644 pages/manage-users-edit.go.html delete mode 100644 theme/theme.go create mode 100644 utils/once.go diff --git a/theme/style.css b/pages/assets/style.css similarity index 100% rename from theme/style.css rename to pages/assets/style.css diff --git a/pages/manage-apps-create.go.html b/pages/manage-apps-create.go.html new file mode 100644 index 0000000..82ed926 --- /dev/null +++ b/pages/manage-apps-create.go.html @@ -0,0 +1,68 @@ + + + + {{.ServiceName}} + + + + +
+

{{.ServiceName}}

+
+
+
+ +
+ +

Create Client Application

+
+ + +
+ + +
+
+ + +
+
+ +
+ {{if .IsAdmin}} +
+ +
+ {{end}} +
+ +
+ +
+
+ + diff --git a/pages/manage-apps-edit.go.html b/pages/manage-apps-edit.go.html new file mode 100644 index 0000000..07834c8 --- /dev/null +++ b/pages/manage-apps-edit.go.html @@ -0,0 +1,75 @@ + + + + {{.ServiceName}} + + + + +
+

{{.ServiceName}}

+
+
+
+ +
+ +

Edit Client Application

+
+ + + +
+ +
+
+ + +
+
+ + +
+
+ +
+ {{if .IsAdmin}} +
+ +
+ {{end}} +
+ +
+ +
+
+ + +
+
+ + diff --git a/pages/manage-apps.go.html b/pages/manage-apps.go.html index 9b4feb0..9d35df9 100644 --- a/pages/manage-apps.go.html +++ b/pages/manage-apps.go.html @@ -41,113 +41,54 @@
New application secret: {{.NewAppSecret}} for {{.NewAppName}}
{{end}} - {{if .Edit}} -

Edit Client Application

-
- - - -
- -
-
- - -
-
- - -
-
- -
- {{if .IsAdmin}} -
- -
- {{end}} -
- -
- -
-
- - -
- {{else}} -

Manage Client Applications

- {{if eq (len .Apps) 0}} -
No client applications found
- {{else}} - - - - - - - - - - - - - - - {{range .Apps}} - - - - - - - - - - - {{end}} - -
IDNameDomainPublicSSOActiveOwnerActions
{{.Subject}}{{.Name}}{{.Domain}}{{.Public}}{{.Sso}}{{.Active}}{{.Owner}} -
- - - -
-
- - - - -
-
- {{end}} +

Manage Client Applications

+
+ +
-

Create Client Application

-
- - -
- - -
-
- - -
-
- -
- {{if .IsAdmin}} -
- -
+ {{if eq (len .Apps) 0}} +
No client applications found
+ {{else}} + + + + + + + + + + + + + + + {{range .Apps}} + + + + + + + + + + {{end}} -
- -
- - + +
IDNameDomainPublicSSOActiveOwnerActions
{{.Subject}}{{.Name}}{{.Domain}}{{.Public}}{{.Sso}}{{.Active}}{{.Owner}} + + + + + +
+ + + + +
+
{{end}} diff --git a/pages/manage-users-create.go.html b/pages/manage-users-create.go.html new file mode 100644 index 0000000..376df91 --- /dev/null +++ b/pages/manage-users-create.go.html @@ -0,0 +1,49 @@ + + + + {{.ServiceName}} + + + +
+

{{.ServiceName}}

+
+
+
+ +
+ +

Create User

+
+ + +
+ + +
+
+ + +
+
+ +

Using an `@{{.Namespace}}` email address will automatically verify as it is owned by this login + service.

+ +
+
+ + +
+
+ +
+ +
+
+ + diff --git a/pages/manage-users-edit.go.html b/pages/manage-users-edit.go.html new file mode 100644 index 0000000..ead2567 --- /dev/null +++ b/pages/manage-users-edit.go.html @@ -0,0 +1,51 @@ + + + + {{.ServiceName}} + + + +
+

{{.ServiceName}}

+
+
+
+ +
+ +

Edit User

+
+ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ + +
+
+ + diff --git a/pages/manage-users.go.html b/pages/manage-users.go.html index 226e2f2..7bc00ff 100644 --- a/pages/manage-users.go.html +++ b/pages/manage-users.go.html @@ -13,142 +13,78 @@ - {{if .Edit}} -

Edit User

-
- - - -
- -
-
- - -
-
- - -
-
- - -
-
- -
- -
+

Manage Users

+
+ +
+ + {{if eq (len .Users) 0}} +
No users found, this is definitely a bug.
+ {{else}} + + + + + + + + + + + + + + + + + + {{range .Users}} + + + + + + + + + + + + + + {{end}} + +
IDNameUsernamePictureWebsiteEmailEmail VerifiedRoleLast UpdatedActiveActions
{{.Subject}}{{.Name}}{{.Username}} + {{if .Picture}} + {{.Name}} Profile Picture + {{end}} + {{.Website}} + {{if $.EmailShow}} + {{.Email}} + {{else}} + {{emailHide .Email}} + {{end}} + {{.EmailVerified}}{{.Role}}{{.UpdatedAt}}{{.Active}} + {{if eq $.CurrentAdmin .Subject}} + + {{else}} +
+ + + +
+
+ + +
+ {{end}} +
- -
- {{else}} -

Manage Users

- {{if eq (len .Users) 0}} -
No users found, this is definitely a bug.
- {{else}} - - - - - - - - - - - - - - - - - - {{range .Users}} - - - - - - - - - - - - - - {{end}} - -
IDNameUsernamePictureWebsiteEmailEmail VerifiedRoleLast UpdatedActiveActions
{{.Subject}}{{.Name}}{{.Username}} - {{if .Picture}} - {{.Name}} Profile Picture - {{end}} - {{.Website}} - {{if $.EmailShow}} - {{.Email}} - {{else}} - {{emailHide .Email}} - {{end}} - {{.EmailVerified}}{{.Role}}{{.UpdatedAt}}{{.Active}} - {{if eq $.CurrentAdmin .Subject}} - - {{else}} -
- - - -
-
- - -
- {{end}} -
-
- - {{if not .EmailShow}} - - {{end}} - -
- {{end}} - -

Create User

-
- - -
- - -
-
- - -
-
- -

Using an `@{{.Namespace}}` email address will automatically verify as it is owned by this login - service.

- -
-
- - -
-
- -
- + {{if not .EmailShow}} + + {{end}} +
{{end}} diff --git a/pages/pages.go b/pages/pages.go index 048963a..9e3e86b 100644 --- a/pages/pages.go +++ b/pages/pages.go @@ -1,34 +1,36 @@ package pages import ( + "bytes" "embed" _ "embed" "errors" "github.com/1f349/overlapfs" "github.com/1f349/tulip/logger" + "github.com/1f349/tulip/utils" "html/template" "io" "io/fs" "os" "path/filepath" - "sync" ) var ( - //go:embed *.go.html + //go:embed *.go.html assets/*.css wwwPages embed.FS wwwTemplates *template.Template - loadOnce sync.Once + loadOnce utils.Once[error] + cssAssetMap = make(map[string][]byte) ) func LoadPages(wd string) (err error) { - loadOnce.Do(func() { + return loadOnce.Do(func() error { var o fs.FS = wwwPages if wd != "" { wwwDir := filepath.Join(wd, "www") err = os.Mkdir(wwwDir, os.ModePerm) if err != nil && !errors.Is(err, os.ErrExist) { - return + return err } wdFs := os.DirFS(wwwDir) o = overlapfs.OverlapFS{A: wwwPages, B: wdFs} @@ -36,8 +38,19 @@ func LoadPages(wd string) (err error) { wwwTemplates, err = template.New("pages").Funcs(template.FuncMap{ "emailHide": EmailHide, }).ParseFS(o, "*.go.html") + + glob, err := fs.Glob(o, "assets/*") + if err != nil { + return err + } + for _, i := range glob { + cssAssetMap[i], err = fs.ReadFile(o, i) + if err != nil { + return err + } + } + return nil }) - return err } func RenderPageTemplate(wr io.Writer, name string, data any) { @@ -48,6 +61,14 @@ func RenderPageTemplate(wr io.Writer, name string, data any) { } } +func RenderCss(name string) io.ReadSeeker { + b, ok := cssAssetMap[name] + if !ok { + return nil + } + return bytes.NewReader(b) +} + func EmailHide(a string) string { b := []byte(a) for i := range b { diff --git a/server/auth_test.go b/server/auth_test.go index 87a73ef..9c87519 100644 --- a/server/auth_test.go +++ b/server/auth_test.go @@ -5,6 +5,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "net/http" + "net/http/httptest" "net/url" "testing" ) @@ -45,9 +46,10 @@ func TestRequireAuthentication(t *testing.T) { func TestOptionalAuthentication(t *testing.T) { h := &HttpServer{} + rec := httptest.NewRecorder() req, err := http.NewRequest(http.MethodGet, "https://example.com/hello", nil) assert.NoError(t, err) - auth, err := h.internalAuthenticationHandler(nil, req) + auth, err := h.internalAuthenticationHandler(rec, req) assert.NoError(t, err) assert.True(t, auth.IsGuest()) auth.Subject = "567" diff --git a/server/manage-apps.go b/server/manage-apps.go index 8584c7c..164b23a 100644 --- a/server/manage-apps.go +++ b/server/manage-apps.go @@ -52,20 +52,41 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _ if q.Has("edit") { for _, i := range appList { if i.Subject == q.Get("edit") { - m["Edit"] = i - goto validEdit + m["EditApp"] = i + rw.Header().Set("Content-Type", "text/html") + rw.WriteHeader(http.StatusOK) + pages.RenderPageTemplate(rw, "manage-apps-edit", m) + return } } http.Error(rw, "400 Bad Request: Invalid client app to edit", http.StatusBadRequest) return } -validEdit: rw.Header().Set("Content-Type", "text/html") rw.WriteHeader(http.StatusOK) pages.RenderPageTemplate(rw, "manage-apps", m) } +func (h *HttpServer) ManageAppsCreateGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { + var roles types.UserRole + if h.DbTx(rw, func(tx *database.Queries) (err error) { + roles, err = tx.GetUserRole(req.Context(), auth.Subject) + return + }) { + return + } + + m := map[string]any{ + "ServiceName": h.conf.ServiceName, + "IsAdmin": roles == types.RoleAdmin, + } + + rw.Header().Set("Content-Type", "text/html") + rw.WriteHeader(http.StatusOK) + pages.RenderPageTemplate(rw, "manage-apps-create", m) +} + func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { err := req.ParseForm() if err != nil { diff --git a/server/manage-users.go b/server/manage-users.go index 719e832..999fae4 100644 --- a/server/manage-users.go +++ b/server/manage-users.go @@ -57,20 +57,42 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _ if q.Has("edit") { for _, i := range userList { if i.Subject == q.Get("edit") { - m["Edit"] = i - goto validEdit + m["EditUser"] = i + rw.Header().Set("Content-Type", "text/html") + rw.WriteHeader(http.StatusOK) + pages.RenderPageTemplate(rw, "manage-users-edit", m) + return } } http.Error(rw, "400 Bad Request: Invalid user to edit", http.StatusBadRequest) return } -validEdit: rw.Header().Set("Content-Type", "text/html") rw.WriteHeader(http.StatusOK) pages.RenderPageTemplate(rw, "manage-users", m) } +func (h *HttpServer) ManageUsersCreateGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { + var roles types.UserRole + if h.DbTx(rw, func(tx *database.Queries) (err error) { + roles, err = tx.GetUserRole(req.Context(), auth.Subject) + return + }) { + return + } + + m := map[string]any{ + "ServiceName": h.conf.ServiceName, + "IsAdmin": roles == types.RoleAdmin, + "Namespace": h.conf.Namespace, + } + + rw.Header().Set("Content-Type", "text/html") + rw.WriteHeader(http.StatusOK) + pages.RenderPageTemplate(rw, "manage-users-create", m) +} + func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { err := req.ParseForm() if err != nil { diff --git a/server/server.go b/server/server.go index b3ce915..daffd52 100644 --- a/server/server.go +++ b/server/server.go @@ -1,7 +1,6 @@ package server import ( - "bytes" "crypto/subtle" _ "embed" "encoding/json" @@ -12,8 +11,8 @@ import ( "github.com/1f349/tulip/database" "github.com/1f349/tulip/logger" "github.com/1f349/tulip/openid" + "github.com/1f349/tulip/pages" scope2 "github.com/1f349/tulip/scope" - "github.com/1f349/tulip/theme" "github.com/go-oauth2/oauth2/v4/errors" "github.com/go-oauth2/oauth2/v4/manage" "github.com/go-oauth2/oauth2/v4/server" @@ -21,6 +20,7 @@ import ( "github.com/julienschmidt/httprouter" "net/http" "net/url" + "path" "strings" "time" ) @@ -52,6 +52,7 @@ type mailLinkKey struct { func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *http.Server { r := httprouter.New() + contentCache := time.Now() // remove last slash from baseUrl { @@ -145,8 +146,14 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt })) // theme styles - r.GET("/theme/style.css", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - http.ServeContent(rw, req, "style.css", time.Now(), bytes.NewReader(theme.DefaultThemeCss)) + r.GET("/assets/*filepath", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { + name := params.ByName("filepath") + if strings.Contains(name, "..") { + http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + out := pages.RenderCss(path.Join("assets", name)) + http.ServeContent(rw, req, path.Base(name), contentCache, out) }) // login steps @@ -168,8 +175,10 @@ func NewHttpServer(conf Conf, db *database.Queries, signingKey mjwt.Signer) *htt // management pages r.GET("/manage/apps", hs.RequireAuthentication(hs.ManageAppsGet)) + r.GET("/manage/apps/create", hs.RequireAuthentication(hs.ManageAppsCreateGet)) r.POST("/manage/apps", hs.RequireAuthentication(hs.ManageAppsPost)) r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet)) + r.GET("/manage/users/create", hs.RequireAuthentication(hs.ManageUsersCreateGet)) r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost)) // oauth pages diff --git a/theme/theme.go b/theme/theme.go deleted file mode 100644 index dd91b87..0000000 --- a/theme/theme.go +++ /dev/null @@ -1,6 +0,0 @@ -package theme - -import _ "embed" - -//go:embed style.css -var DefaultThemeCss []byte diff --git a/utils/once.go b/utils/once.go new file mode 100644 index 0000000..b649851 --- /dev/null +++ b/utils/once.go @@ -0,0 +1,15 @@ +package utils + +import "sync" + +type Once[T any] struct { + once sync.Once + value T +} + +func (o *Once[T]) Do(f func() T) T { + o.once.Do(func() { + o.value = f() + }) + return o.value +}