Permissions:
-Permissions:
+diff --git a/server/auth.go b/server/auth.go index e94cce5..385bb9a 100644 --- a/server/auth.go +++ b/server/auth.go @@ -24,6 +24,7 @@ type UserAuth struct { type SessionData struct { ID string DisplayName string + UserInfo map[string]any } func (u UserAuth) IsGuest() bool { diff --git a/server/login.go b/server/login.go index e84dea5..e3e7ca8 100644 --- a/server/login.go +++ b/server/login.go @@ -1,6 +1,12 @@ package server import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" "github.com/1f349/lavender/pages" "github.com/google/uuid" "github.com/julienschmidt/httprouter" @@ -11,7 +17,12 @@ import ( "time" ) -func (h *HttpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { +func (h *HttpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { + if !auth.IsGuest() { + h.SafeRedirect(rw, req) + return + } + cookie, err := req.Cookie("lavender-login-name") if err == nil && cookie.Valid() == nil { pages.RenderPageTemplate(rw, "login-memory", map[string]any{ @@ -27,7 +38,12 @@ func (h *HttpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httpr }) } -func (h *HttpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { +func (h *HttpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { + if !auth.IsGuest() { + h.SafeRedirect(rw, req) + return + } + if req.PostFormValue("not-you") == "1" { http.SetCookie(rw, &http.Cookie{ Name: "lavender-login-name", @@ -77,3 +93,79 @@ func (h *HttpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http nextUrl := oa2conf.AuthCodeURL(state, oauth2.SetAuthURLParam("login_name", loginUn)) http.Redirect(rw, req, nextUrl, http.StatusFound) } + +func (h *HttpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) { + flowState, ok := h.flowState.Get(req.FormValue("state")) + if !ok { + http.Error(rw, "Invalid flow state", http.StatusBadRequest) + return + } + token, err := flowState.sso.OAuth2Config.Exchange(context.Background(), req.FormValue("code"), oauth2.SetAuthURLParam("redirect_uri", h.conf.BaseUrl+"/callback")) + if err != nil { + http.Error(rw, "Failed to exchange code for token", http.StatusInternalServerError) + return + } + + res, err := flowState.sso.OAuth2Config.Client(context.Background(), token).Get(flowState.sso.UserInfoEndpoint) + if err != nil || res.StatusCode != 200 { + rw.WriteHeader(http.StatusInternalServerError) + if err != nil { + _, _ = rw.Write([]byte(err.Error())) + } else { + _, _ = rw.Write([]byte(res.Status)) + } + return + } + defer res.Body.Close() + + var userInfoJson map[string]any + if err := json.NewDecoder(res.Body).Decode(&userInfoJson); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + subject, ok := userInfoJson["sub"].(string) + if !ok { + http.Error(rw, "Invalid subject", http.StatusInternalServerError) + return + } + subject += "@" + flowState.sso.Config.Namespace + + displayName, ok := userInfoJson["name"].(string) + if !ok { + displayName = "Unknown Name" + } + + // only continues if the above tx succeeds + auth.Data = SessionData{ + ID: subject, + DisplayName: displayName, + UserInfo: userInfoJson, + } + if auth.SaveSessionData() != nil { + http.Error(rw, "Failed to save session", http.StatusInternalServerError) + return + } + + if h.setLoginDataCookie(rw, auth.Data.ID) { + http.Error(rw, "Internal Server Error", http.StatusInternalServerError) + return + } + h.SafeRedirect(rw, req) +} + +func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId string) bool { + encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signingKey.PublicKey(), []byte(userId), []byte("login-data")) + if err != nil { + return true + } + encryptedString := base64.RawStdEncoding.EncodeToString(encryptedData) + http.SetCookie(rw, &http.Cookie{ + Name: "login-data", + Value: encryptedString, + Path: "/", + Expires: time.Now().AddDate(0, 3, 0), + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + return false +} diff --git a/server/server.go b/server/server.go index d33c1d4..b3f9ed3 100644 --- a/server/server.go +++ b/server/server.go @@ -18,6 +18,7 @@ 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" "log" "net/http" @@ -44,6 +45,8 @@ 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 @@ -117,8 +120,9 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser r.GET("/", hs.OptionalAuthentication(hs.Home)) // login - r.GET("/login", hs.loginGet) - r.POST("/login", hs.loginPost) + r.GET("/login", hs.OptionalAuthentication(hs.loginGet)) + 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 { diff --git a/test-client/index.html b/test-client/index.html index 70dab64..a54115b 100644 --- a/test-client/index.html +++ b/test-client/index.html @@ -1,142 +1,92 @@
-Permissions:
-Permissions:
+