diff --git a/API.md b/API.md index 97d598a..07b9c0a 100644 --- a/API.md +++ b/API.md @@ -21,6 +21,7 @@ All routes can be found under `/api/v1/http/` - `/api/v1/systems`: Returns a (publically visible) list of systems - `/api/v1/news`: Returns JSON containing all news items + ## Interactive Websocket API Some API calls require interactivity or provide streaming content to clients. diff --git a/go.mod b/go.mod index 753011e..c674872 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/pquerna/otp v1.4.0 github.com/rs/zerolog v1.29.1 github.com/tkw1536/goprogram v0.3.5 - github.com/tkw1536/pkglib v0.0.0-20230530085130-4f049f64e420 + github.com/tkw1536/pkglib v0.0.0-20230629065114-9b97337c75a0 github.com/yuin/goldmark v1.5.4 github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.8.0 diff --git a/go.sum b/go.sum index c3518cd..799b795 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtp github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tkw1536/goprogram v0.3.5 h1:S0axKo3R/vGa4zhYqYDKAZEPhAfwUSSeMtVwnAu4sNY= github.com/tkw1536/goprogram v0.3.5/go.mod h1:pYr4dMHOSVurbPQ4KTR0ett8XWNISbsRS6zlh9Nsxa8= -github.com/tkw1536/pkglib v0.0.0-20230530085130-4f049f64e420 h1:ZZ7Hhf4cMKXWbOfh6A1FWmhOgD8US0Tz9Ky2fPiaZ3I= -github.com/tkw1536/pkglib v0.0.0-20230530085130-4f049f64e420/go.mod h1:0A1B9Cc5+yJXR3eeB14CqD4dFSbEjjWRo5Pr9M3XYuI= +github.com/tkw1536/pkglib v0.0.0-20230629065114-9b97337c75a0 h1:M1iZRhxJtw9SZSGjowf1IpZx96U5fQK/c3uhK9vZOxk= +github.com/tkw1536/pkglib v0.0.0-20230629065114-9b97337c75a0/go.mod h1:0A1B9Cc5+yJXR3eeB14CqD4dFSbEjjWRo5Pr9M3XYuI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/internal/dis/component/auth/api/api.go b/internal/dis/component/auth/api/api.go index 2865edb..269b29b 100644 --- a/internal/dis/component/auth/api/api.go +++ b/internal/dis/component/auth/api/api.go @@ -43,11 +43,9 @@ func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error Methods: []string{"GET"}, Handler: func(s string, r *http.Request) (ai AuthInfo, err error) { - var user *auth.AuthUser - user, err = a.Dependencies.Auth.SessionOf(r) - if user != nil { - ai.User = user.User.User - } + session, _, err := a.Dependencies.Auth.SessionOf(r) + ai.User = session.Username() + ai.Token = session.Token return }, }, nil diff --git a/internal/dis/component/auth/next/next.go b/internal/dis/component/auth/next/next.go index 043af2e..2ab493f 100644 --- a/internal/dis/component/auth/next/next.go +++ b/internal/dis/component/auth/next/next.go @@ -31,7 +31,7 @@ var ( func (next *Next) Routes() component.Routes { return component.Routes{ Prefix: "/next/", - Decorator: next.Dependencies.Auth.Require(true, scopes.ScopeUserLoggedIn, nil), + Decorator: next.Dependencies.Auth.Require(true, scopes.ScopeUserValid, nil), } } diff --git a/internal/dis/component/auth/panel/panel.go b/internal/dis/component/auth/panel/panel.go index 14a37d9..524b58a 100644 --- a/internal/dis/component/auth/panel/panel.go +++ b/internal/dis/component/auth/panel/panel.go @@ -42,7 +42,7 @@ func (panel *UserPanel) Routes() component.Routes { return component.Routes{ Prefix: "/user/", CSRF: true, - Decorator: panel.Dependencies.Auth.Require(false, scopes.ScopeUserLoggedIn, nil), + Decorator: panel.Dependencies.Auth.Require(false, scopes.ScopeUserValid, nil), } } @@ -137,7 +137,7 @@ func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Han } // ensure that the user is logged in! - return panel.Dependencies.Auth.Protect(router, false, scopes.ScopeUserLoggedIn, nil), nil + return panel.Dependencies.Auth.Protect(router, false, scopes.ScopeUserValid, nil), nil } type userFormContext struct { diff --git a/internal/dis/component/auth/panel/user.go b/internal/dis/component/auth/panel/user.go index 26f2d1b..017db1f 100644 --- a/internal/dis/component/auth/panel/user.go +++ b/internal/dis/component/auth/panel/user.go @@ -62,7 +62,7 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler { return uc, nil, err } - uc.ShowAdminURLs = panel.Dependencies.Auth.CheckScope("", scopes.ScopeAdminLoggedIn, r) == nil + uc.ShowAdminURLs = panel.Dependencies.Auth.CheckScope("", scopes.ScopeUserAdmin, r) == nil // replace the totp action in the menu var totpAction component.MenuItem diff --git a/internal/dis/component/auth/protect.go b/internal/dis/component/auth/protect.go index 237e4b7..97b5631 100644 --- a/internal/dis/component/auth/protect.go +++ b/internal/dis/component/auth/protect.go @@ -22,14 +22,14 @@ func (auth *Auth) Protect(handler http.Handler, AllowToken bool, scope component var paramValue string // load the user in the session - // TODO: Check if API access is allowed - user, token, err := auth.SessionOf(r) + // TODO: In a future version of sessions, check if token has the permitted scope. + session, user, err := auth.SessionOf(r) if err != nil { goto err } // token was set, but not allowed! - if token && !AllowToken { + if session.Token && !AllowToken { goto forbidden } diff --git a/internal/dis/component/auth/scopes/admin.go b/internal/dis/component/auth/scopes/admin.go index 24547c0..24594ba 100644 --- a/internal/dis/component/auth/scopes/admin.go +++ b/internal/dis/component/auth/scopes/admin.go @@ -19,19 +19,19 @@ var ( ) const ( - ScopeAdminLoggedIn Scope = "login.admin" + ScopeUserAdmin Scope = "user.admin" ) func (*AdminLoggedIn) Scope() component.ScopeInfo { return component.ScopeInfo{ - Scope: ScopeAdminLoggedIn, - Description: "session has a signed in admin", - DeniedMessage: "user must be signed into an admin account with TOTP enabled", + Scope: ScopeUserAdmin, + Description: "session must have a valid admin", + DeniedMessage: "user must have an admin account with TOTP enabled", TakesParam: false, } } func (al *AdminLoggedIn) HasScope(param string, r *http.Request) (bool, error) { - user, _, err := al.Dependencies.Auth.SessionOf(r) + _, user, err := al.Dependencies.Auth.SessionOf(r) return user != nil && user.IsAdmin() && user.IsTOTPEnabled(), err } diff --git a/internal/dis/component/auth/scopes/user.go b/internal/dis/component/auth/scopes/user.go index b03ca60..c67a3aa 100644 --- a/internal/dis/component/auth/scopes/user.go +++ b/internal/dis/component/auth/scopes/user.go @@ -19,18 +19,18 @@ var ( ) const ( - ScopeUserLoggedIn Scope = "login.user" + ScopeUserValid Scope = "user.valid" ) func (*UserLoggedIn) Scope() component.ScopeInfo { return component.ScopeInfo{ - Scope: ScopeUserLoggedIn, - Description: "session has an associated user", + Scope: ScopeUserValid, + Description: "session must have a valid user", TakesParam: false, } } func (iu *UserLoggedIn) HasScope(param string, r *http.Request) (bool, error) { - user, _, err := iu.Dependencies.Auth.SessionOf(r) + _, user, err := iu.Dependencies.Auth.SessionOf(r) return user != nil, err } diff --git a/internal/dis/component/auth/session.go b/internal/dis/component/auth/session.go index b0e6981..127e0e9 100644 --- a/internal/dis/component/auth/session.go +++ b/internal/dis/component/auth/session.go @@ -41,11 +41,15 @@ func (auth *Auth) SessionOf(r *http.Request) (session component.SessionInfo, use if err != nil { return component.SessionInfo{}, nil, err } + if user == nil { + return component.SessionInfo{}, nil, nil + } return component.SessionInfo{User: &user.User, Token: false}, user, nil } } // UserOfToken returns the user associated with the token in request. +// To check the user of a token or session, use SessionOf. func (auth *Auth) UserOfToken(r *http.Request) (user *AuthUser, err error) { // get the token object token, err := auth.Dependencies.Tokens.TokenOf(r) diff --git a/internal/dis/component/resolver/resolver.go b/internal/dis/component/resolver/resolver.go index 76da377..12b7d30 100644 --- a/internal/dis/component/resolver/resolver.go +++ b/internal/dis/component/resolver/resolver.go @@ -103,7 +103,7 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H IndexContext: context, } - if resolver.Dependencies.Auth.CheckScope("", scopes.ScopeUserLoggedIn, r) != nil { + if resolver.Dependencies.Auth.CheckScope("", scopes.ScopeUserValid, r) != nil { ctx.IndexContext.Prefixes = nil } httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r) diff --git a/internal/dis/component/server/admin/admin.go b/internal/dis/component/server/admin/admin.go index 46ecee4..a9e008c 100644 --- a/internal/dis/component/server/admin/admin.go +++ b/internal/dis/component/server/admin/admin.go @@ -47,12 +47,12 @@ func (admin *Admin) Routes() component.Routes { return component.Routes{ Prefix: "/admin/", CSRF: true, - Decorator: admin.Dependencies.Auth.Require(false, scopes.ScopeAdminLoggedIn, nil), + Decorator: admin.Dependencies.Auth.Require(false, scopes.ScopeUserAdmin, nil), } } func (admin *Admin) Menu(r *http.Request) []component.MenuItem { - if admin.Dependencies.Auth.CheckScope("", scopes.ScopeAdminLoggedIn, r) != nil { + if admin.Dependencies.Auth.CheckScope("", scopes.ScopeUserAdmin, r) != nil { return nil } return []component.MenuItem{ diff --git a/internal/dis/component/server/list/list.go b/internal/dis/component/server/list/list.go index c038d10..60f9bf2 100644 --- a/internal/dis/component/server/list/list.go +++ b/internal/dis/component/server/list/list.go @@ -42,7 +42,7 @@ func (li *ListInstances) ShouldShowList(r *http.Request) bool { return allowPrivate } - user, _, _ := li.Dependencies.Auth.SessionOf(r) + _, user, _ := li.Dependencies.Auth.SessionOf(r) if user == nil { return allowPublic } else { diff --git a/internal/dis/component/server/server.go b/internal/dis/component/server/server.go index 6a77c94..2e0882b 100644 --- a/internal/dis/component/server/server.go +++ b/internal/dis/component/server/server.go @@ -45,10 +45,11 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht DefaultDomain: slug == "" && ok, } } - publicM.Panic = func(panic any, w http.ResponseWriter, r *http.Request) { + publicM.Panic = func(p any, stack []byte, w http.ResponseWriter, r *http.Request) { // log the panic logger.Error(). - Str("panic", fmt.Sprint(panic)). + Str("panic", fmt.Sprint(p)). + Str("stack", string(stack)). Str("path", r.URL.Path). Msg("panic serving handler")