diff --git a/API.md b/API.md index f821895..97d598a 100644 --- a/API.md +++ b/API.md @@ -17,7 +17,7 @@ Typically each request takes only a second to execute. NOTE: These routes will be documented using a Swagger / OpenAPI definition in the future. All routes can be found under `/api/v1/http/` -- `/api/v1/auth`: Returns user information +- `/api/v1/auth`: Returns api session information - `/api/v1/systems`: Returns a (publically visible) list of systems - `/api/v1/news`: Returns JSON containing all news items @@ -54,4 +54,4 @@ Each input is sent directly to the underlying process. ### Supported Websocket Calls -(to be documented) \ No newline at end of file +(to be documented) diff --git a/internal/dis/component/auth/api/api.go b/internal/dis/component/auth/api/api.go index 3342b10..2865edb 100644 --- a/internal/dis/component/auth/api/api.go +++ b/internal/dis/component/auth/api/api.go @@ -44,7 +44,7 @@ func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error Handler: func(s string, r *http.Request) (ai AuthInfo, err error) { var user *auth.AuthUser - user, ai.Token, err = a.Dependencies.Auth.UserOf(r) + user, err = a.Dependencies.Auth.SessionOf(r) if user != nil { ai.User = user.User.User } diff --git a/internal/dis/component/auth/next/next.go b/internal/dis/component/auth/next/next.go index 0ff7bd5..043af2e 100644 --- a/internal/dis/component/auth/next/next.go +++ b/internal/dis/component/auth/next/next.go @@ -80,7 +80,7 @@ func (next *Next) HandleRoute(ctx context.Context, path string) (http.Handler, e } // get the user - user, _, err := next.Dependencies.Auth.UserOf(r) + user, _, err := next.Dependencies.Auth.SessionOf(r) if err != nil { return "", 0, err } diff --git a/internal/dis/component/auth/protect.go b/internal/dis/component/auth/protect.go index f80d04a..237e4b7 100644 --- a/internal/dis/component/auth/protect.go +++ b/internal/dis/component/auth/protect.go @@ -23,7 +23,7 @@ func (auth *Auth) Protect(handler http.Handler, AllowToken bool, scope component // load the user in the session // TODO: Check if API access is allowed - user, token, err := auth.UserOf(r) + user, token, err := auth.SessionOf(r) if err != nil { goto err } diff --git a/internal/dis/component/auth/scopes/admin.go b/internal/dis/component/auth/scopes/admin.go index f95f968..24547c0 100644 --- a/internal/dis/component/auth/scopes/admin.go +++ b/internal/dis/component/auth/scopes/admin.go @@ -32,6 +32,6 @@ func (*AdminLoggedIn) Scope() component.ScopeInfo { } func (al *AdminLoggedIn) HasScope(param string, r *http.Request) (bool, error) { - user, _, err := al.Dependencies.Auth.UserOf(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 fe22059..b03ca60 100644 --- a/internal/dis/component/auth/scopes/user.go +++ b/internal/dis/component/auth/scopes/user.go @@ -31,6 +31,6 @@ func (*UserLoggedIn) Scope() component.ScopeInfo { } func (iu *UserLoggedIn) HasScope(param string, r *http.Request) (bool, error) { - user, _, err := iu.Dependencies.Auth.UserOf(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 d1e1672..b0e6981 100644 --- a/internal/dis/component/auth/session.go +++ b/internal/dis/component/auth/session.go @@ -18,27 +18,30 @@ import ( _ "embed" ) -// UserOf returns the user logged into the provided request. +// SessionOf returns the session and user logged into the provided request. // token indicates if the user used a token to authenticate, or a browser session was used. // A token takes priority over a user in a session. // // If there is no user associated with the given request, user and error are nil, and token is false. // An invalid session, expired token, or disabled user all result in user = nil. // -// When no UserOf exists in the given session returns nil. -func (auth *Auth) UserOf(r *http.Request) (user *AuthUser, token bool, err error) { +// When no SessionOf exists in the given session returns nil. +func (auth *Auth) SessionOf(r *http.Request) (session component.SessionInfo, user *AuthUser, err error) { // check the user from the token first { user, err := auth.UserOfToken(r) if user != nil && err == nil { - return user, true, nil + return component.SessionInfo{User: &user.User, Token: true}, user, nil } } // fallback to using session { user, err := auth.UserOfSession(r) - return user, false, err + if err != nil { + return component.SessionInfo{}, nil, err + } + return component.SessionInfo{User: &user.User, Token: false}, user, nil } } diff --git a/internal/dis/component/scope.go b/internal/dis/component/scope.go index 9912265..5cfae72 100644 --- a/internal/dis/component/scope.go +++ b/internal/dis/component/scope.go @@ -1,8 +1,11 @@ package component import ( + "encoding/json" "fmt" "net/http" + + "github.com/FAU-CDI/wisski-distillery/internal/models" ) // Scope represents a single permit by a session to perform some action. @@ -58,6 +61,36 @@ type ScopeProvider interface { // Scopes returns information about the scope Scope() ScopeInfo - // Check checks if the given session has access to the given scope + // Check checks if the given session has access to the given scope. HasScope(param string, r *http.Request) (bool, error) + // TODO: move this to a session +} + +// SessionInfo provides information about the current session. +type SessionInfo struct { + // User is the current user associated with the session. + User *models.User + + // Token indicates if the user was authenticated with a Token + Token bool +} + +func (si SessionInfo) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + User string `json:"user"` + Token bool `json:"token"` + }{User: si.Username(), Token: si.Token}) +} + +// Username reports the username associated with this session +func (si SessionInfo) Username() string { + if si.User == nil { + return "" + } + return si.User.User +} + +// Anonymous reports if this Session is associated with a user account +func (si SessionInfo) Anonymous() bool { + return si.Username() != "" } diff --git a/internal/dis/component/server/list/list.go b/internal/dis/component/server/list/list.go index 571fff2..c038d10 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.UserOf(r) + user, _, _ := li.Dependencies.Auth.SessionOf(r) if user == nil { return allowPublic } else { diff --git a/internal/models/user.go b/internal/models/user.go index 8105c8f..bee8735 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -7,14 +7,14 @@ const UserTable = "users" type User struct { Pk uint `gorm:"column:pk;primaryKey"` - User string `gorm:"column:user;not null;unique"` // name of the user - PasswordHash []byte `gorm:"column:password"` // password of the user, hashed + User string `gorm:"column:user;not null;unique"` // name of the user - TOTPEnabled *bool `gorm:"column:totpenabled"` // is totp enabled for the user - TOTPURL string `gorm:"column:totp"` // the totp of the user + PasswordHash []byte `gorm:"column:password" json:"-"` // password of the user, hashed + TOTPEnabled *bool `gorm:"column:totpenabled" json:"-"` // is totp enabled for the user + TOTPURL string `gorm:"column:totp" json:"-"` // the totp of the user - Enabled *bool `gorm:"enabled;not null"` - Admin *bool `gorm:"column:admin;not null"` + Enabled *bool `gorm:"enabled;not null" json:"enabled"` + Admin *bool `gorm:"column:admin;not null" json:"admin"` } func (user *User) HasPassword() bool {