From 374a708899af34c64058a17c9e16d4613e5d7c25 Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Thu, 15 Feb 2024 15:09:14 +0000 Subject: [PATCH] Remove session library --- go.mod | 5 ++--- go.sum | 9 ++++---- server/auth.go | 47 +++++++++++------------------------------- server/home.go | 24 ++++++++++++--------- server/login.go | 22 ++++++++------------ server/manage-apps.go | 12 +++++------ server/manage-users.go | 6 +++--- server/oauth.go | 6 +++--- server/server.go | 19 +++++------------ 9 files changed, 58 insertions(+), 92 deletions(-) diff --git a/go.mod b/go.mod index cff2733..0489191 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,13 @@ require ( github.com/1f349/violet v0.0.13 github.com/MrMelon54/exit-reload v0.0.1 github.com/go-oauth2/oauth2/v4 v4.5.2 - github.com/go-session/session v3.1.2+incompatible github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/subcommands v1.2.0 github.com/google/uuid v1.6.0 github.com/julienschmidt/httprouter v1.3.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.8.4 - golang.org/x/oauth2 v0.16.0 + golang.org/x/oauth2 v0.17.0 ) require ( @@ -30,7 +29,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/btree v1.7.0 // indirect github.com/tidwall/buntdb v1.3.0 // indirect - github.com/tidwall/gjson v1.17.0 // indirect + github.com/tidwall/gjson v1.17.1 // indirect github.com/tidwall/grect v0.1.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index 8ac3ddb..a06cc3b 100644 --- a/go.sum +++ b/go.sum @@ -30,7 +30,6 @@ github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ= github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ= -github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg= github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -125,8 +124,8 @@ github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA= github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU= github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= -github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= @@ -182,8 +181,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/server/auth.go b/server/auth.go index 399c872..5cca1d3 100644 --- a/server/auth.go +++ b/server/auth.go @@ -1,9 +1,9 @@ package server import ( - "fmt" "github.com/1f349/lavender/database" - "github.com/go-session/session" + "github.com/1f349/mjwt" + "github.com/1f349/mjwt/auth" "github.com/julienschmidt/httprouter" "net/http" "net/url" @@ -13,30 +13,18 @@ import ( type UserHandler func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) type UserAuth struct { - Session session.Store - Data SessionData -} - -type SessionData struct { ID string DisplayName string UserInfo UserInfoFields } -func (u UserAuth) IsGuest() bool { - return u.Data.ID == "" -} - -func (u UserAuth) SaveSessionData() error { - u.Session.Set("session-data", u.Data) - return u.Session.Save() -} +func (u UserAuth) IsGuest() bool { return u.ID == "" } func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle { return h.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) { var roles string if h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err = tx.GetUserRoles(auth.Data.ID) + roles, err = tx.GetUserRoles(auth.ID) return }) { return @@ -62,7 +50,7 @@ func (h *HttpServer) RequireAuthentication(next UserHandler) httprouter.Handle { func (h *HttpServer) OptionalAuthentication(next UserHandler) httprouter.Handle { return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - auth, err := internalAuthenticationHandler(rw, req) + auth, err := h.internalAuthenticationHandler(req) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return @@ -75,27 +63,16 @@ func (h *HttpServer) OptionalAuthentication(next UserHandler) httprouter.Handle } } -func internalAuthenticationHandler(rw http.ResponseWriter, req *http.Request) (UserAuth, error) { - ss, err := session.Start(req.Context(), rw, req) - if err != nil { - return UserAuth{}, fmt.Errorf("failed to start session") - } - - // get auth object - userIdRaw, ok := ss.Get("session-data") - if !ok { - return UserAuth{Session: ss}, nil - } - userData, ok := userIdRaw.(SessionData) - if !ok { - ss.Delete("session-data") - err := ss.Save() +func (h *HttpServer) internalAuthenticationHandler(req *http.Request) (UserAuth, error) { + if loginCookie, err := req.Cookie("tulip-login-data"); err == nil { + _, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](h.signingKey, loginCookie.Value) if err != nil { - return UserAuth{Session: ss}, fmt.Errorf("failed to reset invalid session data") + return UserAuth{}, err } + return UserAuth{ID: b.Subject}, nil } - - return UserAuth{Session: ss, Data: userData}, nil + // not logged in + return UserAuth{}, nil } func PrepareRedirectUrl(targetPath string, origin *url.URL) *url.URL { diff --git a/server/home.go b/server/home.go index 6694a1b..05d3b70 100644 --- a/server/home.go +++ b/server/home.go @@ -6,10 +6,21 @@ import ( "github.com/google/uuid" "github.com/julienschmidt/httprouter" "net/http" + "time" ) func (h *HttpServer) Home(rw http.ResponseWriter, _ *http.Request, _ httprouter.Params, auth UserAuth) { rw.Header().Set("Content-Type", "text/html") + lNonce := uuid.NewString() + http.SetCookie(rw, &http.Cookie{ + Name: "tulip-nonce", + Value: lNonce, + Path: "/", + Expires: time.Now().Add(10 * time.Minute), + Secure: true, + SameSite: http.SameSiteLaxMode, + }) + if auth.IsGuest() { pages.RenderPageTemplate(rw, "index-guest", map[string]any{ "ServiceName": h.conf.ServiceName, @@ -19,23 +30,16 @@ func (h *HttpServer) Home(rw http.ResponseWriter, _ *http.Request, _ httprouter. var isAdmin bool h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err := tx.GetUserRoles(auth.Data.ID) + roles, err := tx.GetUserRoles(auth.ID) isAdmin = HasRole(roles, "lavender:admin") return err }) - lNonce := uuid.NewString() - auth.Session.Set("action-nonce", lNonce) - if auth.Session.Save() != nil { - http.Error(rw, "Failed to save session", http.StatusInternalServerError) - return - } - pages.RenderPageTemplate(rw, "index", map[string]any{ "ServiceName": h.conf.ServiceName, "Auth": auth, - "Subject": auth.Data.ID, - "DisplayName": auth.Data.DisplayName, + "Subject": auth.ID, + "DisplayName": auth.DisplayName, "Nonce": lNonce, "IsAdmin": isAdmin, }) diff --git a/server/login.go b/server/login.go index 01973d9..8636ce6 100644 --- a/server/login.go +++ b/server/login.go @@ -127,19 +127,15 @@ func (h *HttpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _ } // only continues if the above tx succeeds - auth.Data = sessionData - if err := auth.SaveSessionData(); err != nil { - http.Error(rw, "Failed to save session", http.StatusInternalServerError) - return - } + auth = sessionData if h.DbTx(rw, func(tx *database.Tx) error { - return tx.UpdateUserToken(auth.Data.ID, token.AccessToken, token.RefreshToken, token.Expiry) + return tx.UpdateUserToken(auth.ID, token.AccessToken, token.RefreshToken, token.Expiry) }) { return } - if h.setLoginDataCookie(rw, auth.Data.ID) { + if h.setLoginDataCookie(rw, auth.ID) { http.Error(rw, "Failed to save login cookie", http.StatusInternalServerError) return } @@ -193,28 +189,28 @@ func (h *HttpServer) readLoginDataCookie(req *http.Request, u *UserAuth) { return } - u.Data, _ = h.fetchUserInfo(sso, &token) + *u, _ = h.fetchUserInfo(sso, &token) } -func (h *HttpServer) fetchUserInfo(sso *issuer.WellKnownOIDC, token *oauth2.Token) (SessionData, error) { +func (h *HttpServer) fetchUserInfo(sso *issuer.WellKnownOIDC, token *oauth2.Token) (UserAuth, error) { res, err := sso.OAuth2Config.Client(context.Background(), token).Get(sso.UserInfoEndpoint) if err != nil || res.StatusCode != http.StatusOK { - return SessionData{}, fmt.Errorf("request failed") + return UserAuth{}, fmt.Errorf("request failed") } defer res.Body.Close() var userInfoJson UserInfoFields if err := json.NewDecoder(res.Body).Decode(&userInfoJson); err != nil { - return SessionData{}, err + return UserAuth{}, err } subject, ok := userInfoJson.GetString("sub") if !ok { - return SessionData{}, fmt.Errorf("invalid subject") + return UserAuth{}, fmt.Errorf("invalid subject") } subject += "@" + sso.Config.Namespace displayName := userInfoJson.GetStringOrDefault("name", "Unknown Name") - return SessionData{ + return UserAuth{ ID: subject, DisplayName: displayName, UserInfo: userInfoJson, diff --git a/server/manage-apps.go b/server/manage-apps.go index 984816e..0232488 100644 --- a/server/manage-apps.go +++ b/server/manage-apps.go @@ -26,11 +26,11 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _ var roles string var appList []database.ClientInfoDbOutput if h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err = tx.GetUserRoles(auth.Data.ID) + roles, err = tx.GetUserRoles(auth.ID) if err != nil { return } - appList, err = tx.GetAppList(auth.Data.ID, HasRole(roles, "lavender:admin"), offset) + appList, err = tx.GetAppList(auth.ID, HasRole(roles, "lavender:admin"), offset) return }) { return @@ -80,7 +80,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ if sso || hasPerms { var roles string if h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err = tx.GetUserRoles(auth.Data.ID) + roles, err = tx.GetUserRoles(auth.ID) return }) { return @@ -98,7 +98,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ switch action { case "create": if h.DbTx(rw, func(tx *database.Tx) error { - return tx.InsertClientApp(name, domain, auth.Data.ID, perms, public, sso, active) + return tx.InsertClientApp(name, domain, auth.ID, perms, public, sso, active) }) { return } @@ -108,7 +108,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ if err != nil { return err } - return tx.UpdateClientApp(sub, auth.Data.ID, name, domain, perms, hasPerms, public, sso, active) + return tx.UpdateClientApp(sub, auth.ID, name, domain, perms, hasPerms, public, sso, active) }) { return } @@ -124,7 +124,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ if err != nil { return err } - secret, err = tx.ResetClientAppSecret(sub, auth.Data.ID) + secret, err = tx.ResetClientAppSecret(sub, auth.ID) return err }) { return diff --git a/server/manage-users.go b/server/manage-users.go index d6cbf65..9882479 100644 --- a/server/manage-users.go +++ b/server/manage-users.go @@ -24,7 +24,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _ var roles string var userList []database.User if h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err = tx.GetUserRoles(auth.Data.ID) + roles, err = tx.GetUserRoles(auth.ID) if err != nil { return } @@ -43,7 +43,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _ "Users": userList, "Offset": offset, "EmailShow": req.URL.Query().Has("show-email"), - "CurrentAdmin": auth.Data.ID, + "CurrentAdmin": auth.ID, } if q.Has("edit") { for _, i := range userList { @@ -71,7 +71,7 @@ func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request, var roles string if h.DbTx(rw, func(tx *database.Tx) (err error) { - roles, err = tx.GetUserRoles(auth.Data.ID) + roles, err = tx.GetUserRoles(auth.ID) return }) { return diff --git a/server/oauth.go b/server/oauth.go index 409f294..4c5ff9e 100644 --- a/server/oauth.go +++ b/server/oauth.go @@ -87,7 +87,7 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request "ServiceName": h.conf.ServiceName, "AppName": appName, "AppDomain": appDomain, - "DisplayName": auth.Data.DisplayName, + "DisplayName": auth.DisplayName, "WantsList": scope.FancyScopeList(scopeList), "ResponseType": form.Get("response_type"), "ResponseMode": form.Get("response_mode"), @@ -125,7 +125,7 @@ func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Re return "", err } - auth, err := internalAuthenticationHandler(rw, req) + auth, err := h.internalAuthenticationHandler(req) if err != nil { return "", err } @@ -147,5 +147,5 @@ func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Re http.Redirect(rw, req, redirectUrl.String(), http.StatusFound) return "", nil } - return auth.Data.ID, nil + return auth.ID, nil } diff --git a/server/server.go b/server/server.go index e831338..036c0ae 100644 --- a/server/server.go +++ b/server/server.go @@ -17,7 +17,6 @@ import ( "github.com/go-oauth2/oauth2/v4/manage" "github.com/go-oauth2/oauth2/v4/server" "github.com/go-oauth2/oauth2/v4/store" - "github.com/go-session/session" "github.com/julienschmidt/httprouter" "golang.org/x/oauth2" "log" @@ -46,8 +45,6 @@ type flowStateData struct { } func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Server { - session.InitManager(session.SetCookieName("lavender_session")) - r := httprouter.New() // remove last slash from baseUrl @@ -126,20 +123,14 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser r.POST("/login", hs.OptionalAuthentication(hs.loginPost)) r.GET("/callback", hs.OptionalAuthentication(hs.loginCallback)) r.POST("/logout", hs.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) { - lNonce, ok := auth.Session.Get("action-nonce") - if !ok { - http.Error(rw, "Missing nonce", http.StatusInternalServerError) + cookie, err := req.Cookie("tulip-nonce") + if err != nil { + http.Error(rw, "Missing nonce", http.StatusBadRequest) return } - if subtle.ConstantTimeCompare([]byte(lNonce.(string)), []byte(req.PostFormValue("nonce"))) == 1 { - auth.Session.Delete("session-data") - if auth.Session.Save() != nil { - http.Error(rw, "Failed to save session", http.StatusInternalServerError) - return - } - + if subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(req.PostFormValue("nonce"))) == 1 { http.SetCookie(rw, &http.Cookie{ - Name: "lavender-login-data", + Name: "tulip-login-data", Path: "/", MaxAge: -1, Secure: true,