mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-25 16:18:32 +02:00
255 lines
8.8 KiB
Go
255 lines
8.8 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCreateUserSession(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
sess := &auth_model.UserSession{
|
|
ID: "test-session-create",
|
|
UserID: 1,
|
|
LoginIP: "192.168.1.1",
|
|
LastIP: "192.168.1.1",
|
|
UserAgent: "Mozilla/5.0 Test",
|
|
LoginMethod: "form",
|
|
}
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), sess))
|
|
unittest.AssertExistsAndLoadBean(t, &auth_model.UserSession{ID: "test-session-create"})
|
|
}
|
|
|
|
func TestGetUserSessionByID(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
sess := &auth_model.UserSession{
|
|
ID: "test-session-get",
|
|
UserID: 2,
|
|
LoginIP: "10.0.0.1",
|
|
LastIP: "10.0.0.1",
|
|
UserAgent: "TestAgent",
|
|
LoginMethod: "oauth2",
|
|
}
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), sess))
|
|
|
|
got, err := auth_model.GetUserSessionByID(t.Context(), "test-session-get")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), got.UserID)
|
|
assert.Equal(t, "10.0.0.1", got.LoginIP)
|
|
assert.Equal(t, "TestAgent", got.UserAgent)
|
|
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "nonexistent")
|
|
assert.True(t, auth_model.IsErrUserSessionNotExist(err))
|
|
}
|
|
|
|
func TestGetUserSessionsByUserID(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
for _, id := range []string{"sess-list-1", "sess-list-2", "sess-list-3"} {
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: id,
|
|
UserID: 5,
|
|
LoginIP: "127.0.0.1",
|
|
LastIP: "127.0.0.1",
|
|
}))
|
|
}
|
|
|
|
sessions, err := auth_model.GetUserSessionsByUserID(t.Context(), 5)
|
|
require.NoError(t, err)
|
|
assert.Len(t, sessions, 3)
|
|
|
|
sessions, err = auth_model.GetUserSessionsByUserID(t.Context(), 99999)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, sessions)
|
|
}
|
|
|
|
func TestInvalidateUserSession(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: "sess-invalidate",
|
|
UserID: 1,
|
|
}))
|
|
|
|
require.NoError(t, auth_model.InvalidateUserSession(t.Context(), "sess-invalidate"))
|
|
|
|
got, err := auth_model.GetUserSessionByID(t.Context(), "sess-invalidate")
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, got.LogoutUnix, "LogoutUnix should be set after invalidation")
|
|
}
|
|
|
|
func TestInvalidateAllUserSessions(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
for _, id := range []string{"sess-all-1", "sess-all-2", "sess-all-3"} {
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: id,
|
|
UserID: 3,
|
|
}))
|
|
}
|
|
|
|
// Invalidate all except sess-all-2
|
|
require.NoError(t, auth_model.InvalidateAllUserSessions(t.Context(), 3, "sess-all-2"))
|
|
|
|
kept, err := auth_model.GetUserSessionByID(t.Context(), "sess-all-2")
|
|
require.NoError(t, err)
|
|
assert.Zero(t, kept.LogoutUnix, "excluded session should not be invalidated")
|
|
|
|
for _, id := range []string{"sess-all-1", "sess-all-3"} {
|
|
got, err := auth_model.GetUserSessionByID(t.Context(), id)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, got.LogoutUnix, "session %s should be invalidated", id)
|
|
}
|
|
}
|
|
|
|
func TestUpdateSessionActivity(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: "sess-activity",
|
|
UserID: 1,
|
|
LastIP: "10.0.0.1",
|
|
}))
|
|
|
|
// Update with same IP — only LastAccessUnix should change
|
|
require.NoError(t, auth_model.UpdateSessionActivity(t.Context(), "sess-activity", "10.0.0.1"))
|
|
got, err := auth_model.GetUserSessionByID(t.Context(), "sess-activity")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "10.0.0.1", got.LastIP)
|
|
assert.Empty(t, got.PrevIP)
|
|
|
|
// Update with new IP — PrevIP should shift
|
|
require.NoError(t, auth_model.UpdateSessionActivity(t.Context(), "sess-activity", "172.16.0.1"))
|
|
got, err = auth_model.GetUserSessionByID(t.Context(), "sess-activity")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "172.16.0.1", got.LastIP)
|
|
assert.Equal(t, "10.0.0.1", got.PrevIP)
|
|
|
|
// Updating a nonexistent session should not error
|
|
require.NoError(t, auth_model.UpdateSessionActivity(t.Context(), "nonexistent", "10.0.0.1"))
|
|
|
|
// Updating an already-logged-out session should be a no-op
|
|
require.NoError(t, auth_model.InvalidateUserSession(t.Context(), "sess-activity"))
|
|
beforeUpdate, err := auth_model.GetUserSessionByID(t.Context(), "sess-activity")
|
|
require.NoError(t, err)
|
|
require.NoError(t, auth_model.UpdateSessionActivity(t.Context(), "sess-activity", "192.168.1.1"))
|
|
afterUpdate, err := auth_model.GetUserSessionByID(t.Context(), "sess-activity")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, beforeUpdate.LastIP, afterUpdate.LastIP, "logged-out session IP should not change")
|
|
assert.Equal(t, beforeUpdate.LastAccessUnix, afterUpdate.LastAccessUnix, "logged-out session timestamp should not change")
|
|
}
|
|
|
|
func TestDeleteUserSessionsByUserID(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
for _, id := range []string{"sess-del-1", "sess-del-2"} {
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: id,
|
|
UserID: 4,
|
|
}))
|
|
}
|
|
|
|
require.NoError(t, auth_model.DeleteUserSessionsByUserID(t.Context(), 4))
|
|
|
|
sessions, err := auth_model.GetUserSessionsByUserID(t.Context(), 4)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, sessions)
|
|
}
|
|
|
|
func TestCleanupExpiredUserSessions(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
now := timeutil.TimeStampNow()
|
|
|
|
// Active session — should survive
|
|
require.NoError(t, auth_model.CreateUserSession(t.Context(), &auth_model.UserSession{
|
|
ID: "sess-cleanup-active",
|
|
UserID: 1,
|
|
LastAccessUnix: now,
|
|
}))
|
|
|
|
// Old logged-out session — should be cleaned up.
|
|
// Use raw engine insert to bypass the "created" auto-fill.
|
|
_, err := db.GetEngine(t.Context()).Insert(&auth_model.UserSession{
|
|
ID: "sess-cleanup-old",
|
|
UserID: 1,
|
|
LogoutUnix: timeutil.TimeStamp(int64(now) - 86400*60),
|
|
LastAccessUnix: timeutil.TimeStamp(int64(now) - 86400*60),
|
|
CreatedUnix: timeutil.TimeStamp(int64(now) - 86400*60),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
retentionSeconds := int64(86400 * 30) // 30 days
|
|
maxLifetime := int64(86400) // 1 day
|
|
require.NoError(t, auth_model.CleanupExpiredUserSessions(t.Context(), retentionSeconds, maxLifetime))
|
|
|
|
// Active session should still exist
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "sess-cleanup-active")
|
|
require.NoError(t, err)
|
|
|
|
// Old session should be gone
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "sess-cleanup-old")
|
|
assert.True(t, auth_model.IsErrUserSessionNotExist(err))
|
|
}
|
|
|
|
func TestCleanupExpiredUserSessionsAbandoned(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
now := timeutil.TimeStampNow()
|
|
retentionSeconds := int64(86400 * 30) // 30 days
|
|
maxLifetime := int64(86400) // 1 day
|
|
|
|
cutoff := int64(now) - maxLifetime - retentionSeconds
|
|
|
|
// Abandoned session clearly older than cutoff — should be cleaned up.
|
|
_, err := db.GetEngine(t.Context()).Insert(&auth_model.UserSession{
|
|
ID: "sess-cleanup-abandoned-old",
|
|
UserID: 1,
|
|
LastAccessUnix: timeutil.TimeStamp(cutoff - 1),
|
|
CreatedUnix: timeutil.TimeStamp(cutoff - 1),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Abandoned session exactly at cutoff — should be preserved (strict < comparison).
|
|
_, err = db.GetEngine(t.Context()).Insert(&auth_model.UserSession{
|
|
ID: "sess-cleanup-abandoned-boundary",
|
|
UserID: 1,
|
|
LastAccessUnix: timeutil.TimeStamp(cutoff),
|
|
CreatedUnix: timeutil.TimeStamp(cutoff),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Abandoned session newer than cutoff — should be preserved.
|
|
_, err = db.GetEngine(t.Context()).Insert(&auth_model.UserSession{
|
|
ID: "sess-cleanup-abandoned-new",
|
|
UserID: 1,
|
|
LastAccessUnix: timeutil.TimeStamp(cutoff + 1),
|
|
CreatedUnix: timeutil.TimeStamp(cutoff + 1),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, auth_model.CleanupExpiredUserSessions(t.Context(), retentionSeconds, maxLifetime))
|
|
|
|
// Clearly old abandoned session should be gone.
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "sess-cleanup-abandoned-old")
|
|
assert.True(t, auth_model.IsErrUserSessionNotExist(err))
|
|
|
|
// Boundary and newer abandoned sessions should still exist.
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "sess-cleanup-abandoned-boundary")
|
|
require.NoError(t, err)
|
|
|
|
_, err = auth_model.GetUserSessionByID(t.Context(), "sess-cleanup-abandoned-new")
|
|
require.NoError(t, err)
|
|
}
|