mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 12:00:51 +00:00
cf89ecd887
The `gitea.com/go-chi/session` package only exists for Gitea, so it moves into `modules/session` to fix its bugs directly. Fixes the flake in https://github.com/go-gitea/gitea/actions/runs/36726154500/job/109923538400. - Sessions are only written back when changed, so a read-only request can't revert a concurrent change or restore a logged-out session, like https://github.com/go-macaron/session/commit/ae808a4a4660c802965c834299ab08f167effd12 - The session cookie is only set once a session holds data - Every backend refreshes the expiry on load and file sessions are written atomically - Also fix https://github.com/go-gitea/gitea/issues/36176 ## ⚠️ BREAKING ⚠️ * the `mysql`, `postgres`, `couchbase` and `memcache` session providers are removed, use `file`, `db` or `redis` instead * login-related cookies are renamed to `gitea_session` and `gitea_remember`, if you'd like to use the old names, set `COOKIE_NAME` and `COOKIE_REMEMBER_NAME` in app.ini --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
145 lines
3.1 KiB
Go
145 lines
3.1 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package session
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/util"
|
|
)
|
|
|
|
type Store interface {
|
|
Set(key, value any) error
|
|
Get(key any) any
|
|
Delete(key any) error
|
|
ID() string
|
|
Release() error
|
|
Flush() error
|
|
Destroy(http.ResponseWriter, *http.Request) error
|
|
Regenerate(http.ResponseWriter, *http.Request)
|
|
}
|
|
|
|
type store struct {
|
|
backend backend
|
|
resp http.ResponseWriter
|
|
lock sync.RWMutex
|
|
sid string
|
|
cookieSID string // the session ID the client holds
|
|
data map[any]any
|
|
stored bool // the backend holds data for sid
|
|
changed bool
|
|
}
|
|
|
|
type contextKeyStruct struct{}
|
|
|
|
var ContextKey = contextKeyStruct{}
|
|
|
|
func (s *store) Set(key, value any) error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
s.data[key] = value
|
|
s.changed = true
|
|
s.sendCookie(s.resp)
|
|
return nil
|
|
}
|
|
|
|
func (s *store) Get(key any) any {
|
|
s.lock.RLock()
|
|
defer s.lock.RUnlock()
|
|
return s.data[key]
|
|
}
|
|
|
|
func (s *store) Delete(key any) error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
if _, ok := s.data[key]; ok {
|
|
delete(s.data, key)
|
|
s.changed = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *store) ID() string {
|
|
s.lock.RLock()
|
|
defer s.lock.RUnlock()
|
|
return s.sid
|
|
}
|
|
|
|
func (s *store) Flush() error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
clear(s.data)
|
|
s.changed = true
|
|
return nil
|
|
}
|
|
|
|
func (s *store) Release() error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
var err error
|
|
switch {
|
|
case !s.changed:
|
|
return nil
|
|
case len(s.data) > 0:
|
|
var data []byte
|
|
if data, err = util.PackData(s.data); err == nil {
|
|
err = s.backend.save(s.sid, data, !s.stored)
|
|
}
|
|
case s.stored:
|
|
err = s.backend.destroy(s.sid)
|
|
}
|
|
if err == nil {
|
|
s.changed, s.stored = false, len(s.data) > 0
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *store) Destroy(resp http.ResponseWriter, _ *http.Request) error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
err := s.backend.destroy(s.sid)
|
|
cookie := newCookie("")
|
|
cookie.MaxAge = -1
|
|
http.SetCookie(resp, cookie)
|
|
if err == nil {
|
|
s.sid, s.stored = newSessionID(), false
|
|
}
|
|
s.cookieSID, s.changed = "", err != nil // Release retries a failed destroy
|
|
clear(s.data)
|
|
return err
|
|
}
|
|
|
|
// Regenerate moves the session data to a new session ID, so an ID known before sign-in is never authenticated
|
|
func (s *store) Regenerate(resp http.ResponseWriter, req *http.Request) {
|
|
for _, f := range BeforeRegenerateSession {
|
|
f(resp, req)
|
|
}
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
if s.stored {
|
|
if err := s.backend.destroy(s.sid); err != nil {
|
|
log.Error("Unable to destroy the regenerated session: %v", err)
|
|
}
|
|
}
|
|
s.sid, s.stored, s.changed = newSessionID(), false, true
|
|
s.sendCookie(resp)
|
|
}
|
|
|
|
func (s *store) sendCookie(resp http.ResponseWriter) {
|
|
if s.cookieSID != s.sid && len(s.data) > 0 {
|
|
http.SetCookie(resp, newCookie(s.sid))
|
|
s.cookieSID = s.sid
|
|
}
|
|
}
|
|
|
|
func GetContextSession(req *http.Request) Store {
|
|
sess, _ := req.Context().Value(ContextKey).(Store)
|
|
return sess
|
|
}
|
|
|
|
// BeforeRegenerateSession is a list of functions that are called before a session is regenerated.
|
|
var BeforeRegenerateSession []func(http.ResponseWriter, *http.Request)
|