mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 08:45:33 +02:00
Add user session management and auditing
This commit is contained in:
25 files changed
+1299
-11
No files matched your search
+21
-1
@@ -7,13 +7,16 @@ package auth
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/webauthn"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
)
|
||||
@@ -39,7 +42,7 @@ func Init() {
|
||||
}
|
||||
|
||||
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
|
||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User, loginMethod string) {
|
||||
// We need to regenerate the session...
|
||||
newSess, err := session.RegenerateSession(resp, req)
|
||||
if err != nil {
|
||||
@@ -65,6 +68,23 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
|
||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
||||
}
|
||||
|
||||
// Create tracked user session record
|
||||
ip := req.RemoteAddr
|
||||
if host, _, err := net.SplitHostPort(ip); err == nil {
|
||||
ip = host
|
||||
}
|
||||
if err := auth_model.CreateUserSession(req.Context(), &auth_model.UserSession{
|
||||
ID: sess.ID(),
|
||||
UserID: user.ID,
|
||||
LoginIP: ip,
|
||||
LastIP: ip,
|
||||
UserAgent: req.UserAgent(),
|
||||
LoginMethod: loginMethod,
|
||||
LastAccessUnix: timeutil.TimeStampNow(),
|
||||
}); err != nil {
|
||||
log.Error("Failed to create user session record: %v", err)
|
||||
}
|
||||
|
||||
// Language setting of the user overwrites the one previously set
|
||||
// If the user does not have a locale set, we save the current one.
|
||||
if len(user.Language) == 0 {
|
||||
|
||||
@@ -119,7 +119,7 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da
|
||||
|
||||
if r.CreateSession {
|
||||
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
|
||||
handleSignIn(w, req, sess, user)
|
||||
handleSignIn(w, req, sess, user, "reverse_proxy")
|
||||
}
|
||||
}
|
||||
store.GetData()["IsReverseProxy"] = true
|
||||
|
||||
@@ -121,7 +121,7 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,
|
||||
}
|
||||
|
||||
if s.CreateSession {
|
||||
handleSignIn(w, req, sess, user)
|
||||
handleSignIn(w, req, sess, user, "sspi")
|
||||
}
|
||||
|
||||
log.Trace("SSPI Authorization: Logged in user %-v", user)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/system"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
@@ -171,6 +172,22 @@ func registerDeleteOldSystemNotices() {
|
||||
})
|
||||
}
|
||||
|
||||
func registerCleanupUserSessions() {
|
||||
RegisterTaskFatal("cleanup_user_sessions", &OlderThanConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 24h",
|
||||
},
|
||||
OlderThan: time.Hour * 24 * 30, // 30 day retention
|
||||
}, func(ctx context.Context, _ *user_model.User, config Config) error {
|
||||
olderThanConfig := config.(*OlderThanConfig)
|
||||
retentionSeconds := int64(olderThanConfig.OlderThan.Seconds())
|
||||
maxLifetime := setting.SessionConfig.Maxlifetime
|
||||
return auth_model.CleanupExpiredUserSessions(ctx, retentionSeconds, maxLifetime)
|
||||
})
|
||||
}
|
||||
|
||||
type GCLFSConfig struct {
|
||||
BaseConfig
|
||||
OlderThan time.Duration
|
||||
@@ -239,4 +256,5 @@ func initExtendedTasks() {
|
||||
registerDeleteOldSystemNotices()
|
||||
registerGCLFS()
|
||||
registerRebuildIssueIndexer()
|
||||
registerCleanupUserSessions()
|
||||
}
|
||||
@@ -411,6 +411,17 @@ func (f *WebauthnDeleteForm) Validate(req *http.Request, errs binding.Errors) bi
|
||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// RevokeSessionForm for revoking a user session
|
||||
type RevokeSessionForm struct {
|
||||
SessionID string `binding:"Required"`
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *RevokeSessionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetValidateContext(req)
|
||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// PackageSettingForm form for package settings
|
||||
type PackageSettingForm struct {
|
||||
Action string
|
||||
|
||||
@@ -193,6 +193,10 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
||||
return fmt.Errorf("DeleteAuthTokensByUserID: %w", err)
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteUserSessionsByUserID(ctx, u.ID); err != nil {
|
||||
return fmt.Errorf("DeleteUserSessionsByUserID: %w", err)
|
||||
}
|
||||
|
||||
if _, err = db.DeleteByID[user_model.User](ctx, u.ID); err != nil {
|
||||
return fmt.Errorf("delete: %w", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user