From 72a512bb4f26b14c301a8459436e88ec7bd0b290 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Sun, 6 Jul 2025 14:45:36 +0300 Subject: [PATCH] feat: automatic termination of older sessions on login (temporary until release 4); fix: login controller method not returning tokens --- backend/internal/controllers/auth.go | 10 ++-------- backend/internal/database/query.sql.go | 12 ++++++++++++ backend/internal/services/auth.go | 9 +++++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/backend/internal/controllers/auth.go b/backend/internal/controllers/auth.go index b98f52a..75a8927 100644 --- a/backend/internal/controllers/auth.go +++ b/backend/internal/controllers/auth.go @@ -50,7 +50,6 @@ func NewAuthController(_log *zap.Logger, as services.AuthService) AuthController return &authControllerImpl{log: _log, authService: as} } -// Login implements AuthController. // @Summary Acquire tokens via login credentials (and 2FA code if needed) // @Tags Auth // @Accept json @@ -65,7 +64,7 @@ func (a *authControllerImpl) Login(c *gin.Context) { return } - _, err := a.authService.Login(request.Body) + response, err := a.authService.Login(request.Body) if err != nil { if errors.Is(err, errs.ErrForbidden) { @@ -76,11 +75,10 @@ func (a *authControllerImpl) Login(c *gin.Context) { return } - c.Status(http.StatusOK) + c.JSON(http.StatusOK, response) return } -// PasswordResetBegin implements AuthController. // @Summary Request password reset email // @Tags Auth // @Accept json @@ -90,7 +88,6 @@ func (a *authControllerImpl) PasswordResetBegin(c *gin.Context) { c.Status(http.StatusNotImplemented) } -// PasswordResetComplete implements AuthController. // @Summary Complete password reset with email code and provide 2FA code or backup code if needed // @Tags Auth // @Accept json @@ -100,7 +97,6 @@ func (a *authControllerImpl) PasswordResetComplete(c *gin.Context) { c.Status(http.StatusNotImplemented) } -// Refresh implements AuthController. // @Summary Receive new tokens via refresh token // @Tags Auth // @Accept json @@ -110,7 +106,6 @@ func (a *authControllerImpl) Refresh(c *gin.Context) { c.Status(http.StatusNotImplemented) } -// RegistrationComplete implements AuthController. // @Summary Register an account // @Tags Auth // @Accept json @@ -142,7 +137,6 @@ func (a *authControllerImpl) RegistrationBegin(c *gin.Context) { return } -// RegistrationBegin implements AuthController. // @Summary Confirm with code, finish creating the account // @Tags Auth // @Accept json diff --git a/backend/internal/database/query.sql.go b/backend/internal/database/query.sql.go index 82cf20b..4768f10 100644 --- a/backend/internal/database/query.sql.go +++ b/backend/internal/database/query.sql.go @@ -664,6 +664,18 @@ func (q *Queries) PruneTerminatedSessions(ctx context.Context) error { return err } +const terminateAllSessionsForUserByUsername = `-- name: TerminateAllSessionsForUserByUsername :exec +UPDATE sessions +SET terminated = TRUE +FROM users +WHERE sessions.user_id = users.id AND users.username = $1::text +` + +func (q *Queries) TerminateAllSessionsForUserByUsername(ctx context.Context, username string) error { + _, err := q.db.Exec(ctx, terminateAllSessionsForUserByUsername, username) + return err +} + const updateBannedUser = `-- name: UpdateBannedUser :exec UPDATE banned_users SET diff --git a/backend/internal/services/auth.go b/backend/internal/services/auth.go index 0136b12..71ab9bd 100644 --- a/backend/internal/services/auth.go +++ b/backend/internal/services/auth.go @@ -303,6 +303,15 @@ func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginRespo return nil, returnedError } + // Until release 4, only 1 session at a time is supported + if err = db.TXQueries.TerminateAllSessionsForUserByUsername(db.CTX, request.Username); err != nil { + a.log.Error( + "Failed to terminate older sessions for user trying to log in", + zap.String("username", request.Username), + zap.Error(err)) + return nil, errs.ErrServerError + } + session, err = db.TXQueries.CreateSession(db.CTX, database.CreateSessionParams{ // TODO: use actual values for session metadata UserID: userRow.ID,