package handlers import ( "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "errors" "golias/config" "golias/pkg/cache" "log/slog" "net/http" ) var ( cfg config.Config memcache cache.Cache ) func GetSession(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookieName := "session_token" sessionCookie, err := r.Cookie(cookieName) if err != nil { msg := "auth failed; failed to get session token from cookies" slog.Debug(msg, "error", err) next.ServeHTTP(w, r) return } cookieValueB, err := base64.URLEncoding. DecodeString(sessionCookie.Value) if err != nil { msg := "auth failed; failed to decode b64 cookie" slog.Debug(msg, "error", err) next.ServeHTTP(w, r) return } cookieValue := string(cookieValueB) if len(cookieValue) < sha256.Size { slog.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(cfg.CookieSecret)) mac.Write([]byte(cookieName)) mac.Write([]byte(sessionToken)) expectedSignature := mac.Sum(nil) if !hmac.Equal([]byte(signature), expectedSignature) { slog.Debug("cookie with an invalid sign") next.ServeHTTP(w, r) return } userSession, err := cacheGetSession(sessionToken) if err != nil { msg := "auth failed; session does not exists" err = errors.New(msg) slog.Debug(msg, "error", err) next.ServeHTTP(w, r) return } if userSession.IsExpired() { memcache.RemoveKey(sessionToken) msg := "session is expired" slog.Debug(msg, "error", err, "token", sessionToken) next.ServeHTTP(w, r) return } ctx := context.WithValue(r.Context(), "username", userSession.Username) if err := cacheSetSession(sessionToken, userSession); err != nil { msg := "failed to marshal user session" slog.Warn(msg, "error", err) next.ServeHTTP(w, r) return } next.ServeHTTP(w, r.WithContext(ctx)) }) }