diff options
author | GrailFinder <wohilas@gmail.com> | 2024-05-05 08:57:23 +0300 |
---|---|---|
committer | GrailFinder <wohilas@gmail.com> | 2024-05-05 08:57:23 +0300 |
commit | ff86222fc9ab85fb4c5c5e8a063083595b323761 (patch) | |
tree | 01dbec5503bfabc21af93acdbfe3d1000e2386a0 | |
parent | 8d66ec58e2256412a2fd50ad9e651c09af1ea8cc (diff) |
Enha: protected cookies
-rw-r--r-- | cmd/start.go | 3 | ||||
-rw-r--r-- | components/add_action_form.html | 2 | ||||
-rw-r--r-- | config/config.go | 1 | ||||
-rw-r--r-- | internal/handlers/auth.go | 23 | ||||
-rw-r--r-- | internal/handlers/middleware.go | 37 | ||||
-rw-r--r-- | internal/models/auth.go | 5 |
6 files changed, 57 insertions, 14 deletions
diff --git a/cmd/start.go b/cmd/start.go index 5d2f5b9..c75f0f8 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -21,7 +21,8 @@ var startCmd = &cobra.Command{ Short: "Start data server", Long: `Start data server`, Run: func(cmd *cobra.Command, args []string) { - log := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + log := slog.New(slog.NewJSONHandler(os.Stdout, + &slog.HandlerOptions{Level: slog.LevelDebug})) // load server configuration from server log.Debug("Loading server configuration") if viper.ConfigFileUsed() != "" { diff --git a/components/add_action_form.html b/components/add_action_form.html index ac65ca0..4152dc4 100644 --- a/components/add_action_form.html +++ b/components/add_action_form.html @@ -20,6 +20,6 @@ {{define "showformbtn"}} <div id="actionform"> - <button button id="create-form-btn" type="submit" class="justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" hx-get="/showform" hx-target="#actionform">SHOW FORM</button> + <button button id="create-form-btn" type="submit" class="justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" hx-get="/showform" hx-target="#actionform">CREATE ACTION</button> </div> {{end}} diff --git a/config/config.go b/config/config.go index e2225d8..36b61a8 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type Config struct { BaseURL string `mapstructure:"BASE_URL"` SessionLifetime int `mapstructure:"SESSION_LIFETIME_SECONDS"` DBURI string `mapstructure:"DBURI"` + CookieSecret string `mapstructure:"COOKIE_SECRET"` } type ServerConfig struct { diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 5ec1c80..e7eca50 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -3,6 +3,9 @@ package handlers import ( "apjournal/internal/models" "apjournal/pkg/utils" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" "encoding/json" "html/template" "net/http" @@ -61,6 +64,7 @@ func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { } func (h *Handlers) makeCookie(username string, remote string) (*http.Cookie, error) { + // secret // Create a new random session token // sessionToken := xid.New().String() sessionToken := "token" @@ -70,10 +74,18 @@ func (h *Handlers) makeCookie(username string, remote string) (*http.Cookie, err Username: username, Expiry: expiresAt, } - // TODO: write session to db + cookieName := "session_token" + // hmac to protect cookies + hm := hmac.New(sha256.New, []byte(h.cfg.CookieSecret)) + hm.Write([]byte(cookieName)) + hm.Write([]byte(sessionToken)) + signature := hm.Sum(nil) + // b64 enc to avoid non-ascii + cookieValue := base64.URLEncoding.EncodeToString([]byte( + string(signature) + sessionToken)) cookie := &http.Cookie{ - Name: "session_token", - Value: sessionToken, + Name: cookieName, + Value: cookieValue, Secure: true, HttpOnly: true, SameSite: http.SameSiteNoneMode, @@ -86,7 +98,10 @@ func (h *Handlers) makeCookie(username string, remote string) (*http.Cookie, err cookie.Domain = "192.168.0.101" } // set ctx? - // c.Set("username", username) + // set user in session + if err := h.cacheSetSession(sessionToken, session); err != nil { + return nil, err + } return cookie, nil } diff --git a/internal/handlers/middleware.go b/internal/handlers/middleware.go index 28ccdbc..8b871a2 100644 --- a/internal/handlers/middleware.go +++ b/internal/handlers/middleware.go @@ -2,24 +2,51 @@ package handlers import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" "errors" "net/http" ) func (h *Handlers) GetSession(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - sessionCookie, err := r.Cookie("session_token") + cookieName := "session_token" + sessionCookie, err := r.Cookie(cookieName) if err != nil { msg := "auth failed; failed to get session token from cookies" h.log.Debug(msg, "error", err) next.ServeHTTP(w, r) return } - sessionToken := "" - if sessionCookie.Value == "" { - sessionToken = sessionCookie.Value + cookieValueB, err := base64.URLEncoding. + DecodeString(sessionCookie.Value) + if err != nil { + msg := "auth failed; failed to decode b64 cookie" + h.log.Debug(msg, "error", err) + next.ServeHTTP(w, r) + return + } + cookieValue := string(cookieValueB) + if len(cookieValue) < sha256.Size { + h.log.Warn("small cookie", "size", len(cookieValue)) + next.ServeHTTP(w, r) + return + } + // Split apart the signature and original cookie value. + signature := cookieValue[:sha256.Size] + sessionToken := cookieValue[sha256.Size:] + //verify signature + mac := hmac.New(sha256.New, []byte(h.cfg.CookieSecret)) + mac.Write([]byte(cookieName)) + mac.Write([]byte(sessionToken)) + expectedSignature := mac.Sum(nil) + if !hmac.Equal([]byte(signature), expectedSignature) { + h.log.Debug("cookie with an invalid sign") + next.ServeHTTP(w, r) + return } - userSession, err := h.cacheGetSession(sessionCookie.Value) + userSession, err := h.cacheGetSession(sessionToken) if err != nil { msg := "auth failed; session does not exists" err = errors.New(msg) diff --git a/internal/models/auth.go b/internal/models/auth.go index 5dadf8a..9964cd5 100644 --- a/internal/models/auth.go +++ b/internal/models/auth.go @@ -6,9 +6,8 @@ import ( // each session contains the username of the user and the time at which it expires type Session struct { - Username string - CurrentRoom string - Expiry time.Time + Username string + Expiry time.Time } // we'll use this method later to determine if the session has expired |