satellite/console/dbcleanup: make chore clean up webapp sessions
The console DB cleanup chore has been extended to remove expired webapp session records. Resolves #5893 Change-Id: I455b4933552cfde86817a2ef8f9879dd7b0a121d
This commit is contained in:
parent
0d0e8cc8cf
commit
faf5b960ff
@ -23,7 +23,9 @@ type WebappSessions interface {
|
|||||||
// DeleteAllByUserID deletes all webapp sessions by user ID.
|
// DeleteAllByUserID deletes all webapp sessions by user ID.
|
||||||
DeleteAllByUserID(ctx context.Context, userID uuid.UUID) (int64, error)
|
DeleteAllByUserID(ctx context.Context, userID uuid.UUID) (int64, error)
|
||||||
// UpdateExpiration updates the expiration time of the session.
|
// UpdateExpiration updates the expiration time of the session.
|
||||||
UpdateExpiration(ctx context.Context, sessionID uuid.UUID, expiresAt time.Time) (err error)
|
UpdateExpiration(ctx context.Context, sessionID uuid.UUID, expiresAt time.Time) error
|
||||||
|
// DeleteExpired deletes all sessions that have expired before the provided timestamp.
|
||||||
|
DeleteExpired(ctx context.Context, now time.Time, asOfSystemTimeInterval time.Duration, pageSize int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebappSession represents a session on the satellite web app.
|
// WebappSession represents a session on the satellite web app.
|
||||||
|
@ -54,6 +54,12 @@ func (chore *Chore) Run(ctx context.Context) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
chore.log.Error("Error deleting unverified users", zap.Error(err))
|
chore.log.Error("Error deleting unverified users", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = chore.db.WebappSessions().DeleteExpired(ctx, time.Now(), chore.config.AsOfSystemTimeInterval, chore.config.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
chore.log.Error("Error deleting expired webapp sessions", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ func (db *ConsoleDB) ResetPasswordTokens() console.ResetPasswordTokens {
|
|||||||
|
|
||||||
// WebappSessions is a getter for WebappSessions repository.
|
// WebappSessions is a getter for WebappSessions repository.
|
||||||
func (db *ConsoleDB) WebappSessions() consoleauth.WebappSessions {
|
func (db *ConsoleDB) WebappSessions() consoleauth.WebappSessions {
|
||||||
return &webappSessions{db.methods}
|
return &webappSessions{db.db}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountFreezeEvents is a getter for AccountFreezeEvents repository.
|
// AccountFreezeEvents is a getter for AccountFreezeEvents repository.
|
||||||
|
@ -5,6 +5,8 @@ package satellitedb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
@ -16,7 +18,7 @@ import (
|
|||||||
var _ consoleauth.WebappSessions = (*webappSessions)(nil)
|
var _ consoleauth.WebappSessions = (*webappSessions)(nil)
|
||||||
|
|
||||||
type webappSessions struct {
|
type webappSessions struct {
|
||||||
db dbx.Methods
|
db *satelliteDB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a webapp session and returns the session info.
|
// Create creates a webapp session and returns the session info.
|
||||||
@ -91,6 +93,75 @@ func (db *webappSessions) DeleteAllByUserID(ctx context.Context, userID uuid.UUI
|
|||||||
return db.db.Delete_WebappSession_By_UserId(ctx, dbx.WebappSession_UserId(userID.Bytes()))
|
return db.db.Delete_WebappSession_By_UserId(ctx, dbx.WebappSession_UserId(userID.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteExpired deletes all sessions that have expired before the provided timestamp.
|
||||||
|
func (db *webappSessions) DeleteExpired(ctx context.Context, now time.Time, asOfSystemTimeInterval time.Duration, pageSize int) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
if pageSize <= 0 {
|
||||||
|
return Error.New("expected page size to be positive; got %d", pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageCursor, pageEnd uuid.UUID
|
||||||
|
aost := db.db.impl.AsOfSystemInterval(asOfSystemTimeInterval)
|
||||||
|
for {
|
||||||
|
// Select the ID beginning this page of records
|
||||||
|
err := db.db.QueryRowContext(ctx, `
|
||||||
|
SELECT id FROM webapp_sessions
|
||||||
|
`+aost+`
|
||||||
|
WHERE id > $1 AND expires_at < $2
|
||||||
|
ORDER BY id LIMIT 1
|
||||||
|
`, pageCursor, now).Scan(&pageCursor)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the ID ending this page of records
|
||||||
|
err = db.db.QueryRowContext(ctx, `
|
||||||
|
SELECT id FROM webapp_sessions
|
||||||
|
`+aost+`
|
||||||
|
WHERE id > $1
|
||||||
|
ORDER BY id LIMIT 1 OFFSET $2
|
||||||
|
`, pageCursor, pageSize).Scan(&pageEnd)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
// Since this is the last page, we want to return all remaining records
|
||||||
|
_, err = db.db.ExecContext(ctx, `
|
||||||
|
DELETE FROM webapp_sessions
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT id FROM webapp_sessions
|
||||||
|
`+aost+`
|
||||||
|
WHERE id >= $1 AND expires_at < $2
|
||||||
|
ORDER BY id
|
||||||
|
)
|
||||||
|
`, pageCursor, now)
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all expired records in the range between the beginning and ending IDs
|
||||||
|
_, err = db.db.ExecContext(ctx, `
|
||||||
|
DELETE FROM webapp_sessions
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT id FROM webapp_sessions
|
||||||
|
`+aost+`
|
||||||
|
WHERE id BETWEEN $1 AND $2
|
||||||
|
AND expires_at < $3
|
||||||
|
ORDER BY id
|
||||||
|
)
|
||||||
|
`, pageCursor, pageEnd, now)
|
||||||
|
if err != nil {
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the cursor to the next page
|
||||||
|
pageCursor = pageEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getSessionFromDBX(dbxSession *dbx.WebappSession) (consoleauth.WebappSession, error) {
|
func getSessionFromDBX(dbxSession *dbx.WebappSession) (consoleauth.WebappSession, error) {
|
||||||
id, err := uuid.FromBytes(dbxSession.Id)
|
id, err := uuid.FromBytes(dbxSession.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package satellitedb_test
|
package satellitedb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -186,3 +187,26 @@ func TestWebappSessionsDeleteAllByUserID(t *testing.T) {
|
|||||||
require.Len(t, allSessions, 0)
|
require.Len(t, allSessions, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteExpired(t *testing.T) {
|
||||||
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||||
|
sessionsDB := db.Console().WebappSessions()
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
// Only positive page sizes should be allowed.
|
||||||
|
require.Error(t, sessionsDB.DeleteExpired(ctx, time.Time{}, 0, 0))
|
||||||
|
require.Error(t, sessionsDB.DeleteExpired(ctx, time.Time{}, 0, -1))
|
||||||
|
|
||||||
|
newSession, err := sessionsDB.Create(ctx, testrand.UUID(), testrand.UUID(), "", "", now.Add(time.Second))
|
||||||
|
require.NoError(t, err)
|
||||||
|
oldSession, err := sessionsDB.Create(ctx, testrand.UUID(), testrand.UUID(), "", "", now.Add(-time.Second))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, sessionsDB.DeleteExpired(ctx, now, 0, 1))
|
||||||
|
|
||||||
|
// Ensure that the old session record was deleted and the other remains.
|
||||||
|
_, err = sessionsDB.GetBySessionID(ctx, oldSession.ID)
|
||||||
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||||
|
_, err = sessionsDB.GetBySessionID(ctx, newSession.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user