From c9684d1fe9de8b36b1d460aec84cc0460d4db118 Mon Sep 17 00:00:00 2001 From: Frederic Jahn Date: Fri, 14 Feb 2025 21:00:38 +0100 Subject: [PATCH] fix: create session in DB for old endpoints (#2052) * fix: create session in DB for old endpoints The old endpoints do not store the session in the DB, this leads to an unauthorized error in old hanko elements versions prior 1.0.0 when any endpoint is called that requires a session because they check if the session is stored in the DB. * test: fix test --- backend/handler/passcode.go | 7 ++++- backend/handler/password.go | 7 ++++- backend/handler/token.go | 7 ++++- backend/handler/utils.go | 52 +++++++++++++++++++++++++++++++++++++ backend/handler/webauthn.go | 7 ++++- backend/test/config.go | 1 + 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/backend/handler/passcode.go b/backend/handler/passcode.go index 62b76a99f..523e184df 100644 --- a/backend/handler/passcode.go +++ b/backend/handler/passcode.go @@ -401,7 +401,7 @@ func (h *PasscodeHandler) Finish(c echo.Context) error { emailJwt = dto.JwtFromEmailModel(e) } - token, _, err := h.sessionManager.GenerateJWT(*passcode.UserId, emailJwt) + token, rawToken, err := h.sessionManager.GenerateJWT(*passcode.UserId, emailJwt) if err != nil { return fmt.Errorf("failed to generate jwt: %w", err) } @@ -411,6 +411,11 @@ func (h *PasscodeHandler) Finish(c echo.Context) error { return fmt.Errorf("failed to create session token: %w", err) } + err = storeSession(h.cfg, h.persister, *passcode.UserId, rawToken, c, tx) + if err != nil { + return fmt.Errorf("failed to store session in DB: %w", err) + } + c.Response().Header().Set("X-Session-Lifetime", fmt.Sprintf("%d", cookie.MaxAge)) if h.cfg.Session.EnableAuthTokenHeader { diff --git a/backend/handler/password.go b/backend/handler/password.go index ce8dbe2cc..4647d8fc2 100644 --- a/backend/handler/password.go +++ b/backend/handler/password.go @@ -223,7 +223,7 @@ func (h *PasswordHandler) Login(c echo.Context) error { emailJwt = dto.JwtFromEmailModel(e) } - token, _, err := h.sessionManager.GenerateJWT(pw.UserId, emailJwt) + token, rawToken, err := h.sessionManager.GenerateJWT(pw.UserId, emailJwt) if err != nil { return fmt.Errorf("failed to generate jwt: %w", err) } @@ -233,6 +233,11 @@ func (h *PasswordHandler) Login(c echo.Context) error { return fmt.Errorf("failed to create session cookie: %w", err) } + err = storeSession(h.cfg, h.persister, userId, rawToken, c, h.persister.GetConnection()) + if err != nil { + return fmt.Errorf("failed to store session in DB: %w", err) + } + c.Response().Header().Set("X-Session-Lifetime", fmt.Sprintf("%d", cookie.MaxAge)) if h.cfg.Session.EnableAuthTokenHeader { diff --git a/backend/handler/token.go b/backend/handler/token.go index 4de2daec2..a0246a1b0 100644 --- a/backend/handler/token.go +++ b/backend/handler/token.go @@ -92,7 +92,7 @@ func (h TokenHandler) Validate(c echo.Context) error { emailJwt = dto.JwtFromEmailModel(e) } - jwtToken, _, err := h.sessionManager.GenerateJWT(token.UserID, emailJwt) + jwtToken, rawToken, err := h.sessionManager.GenerateJWT(token.UserID, emailJwt) if err != nil { return fmt.Errorf("failed to generate jwt: %w", err) } @@ -102,6 +102,11 @@ func (h TokenHandler) Validate(c echo.Context) error { return fmt.Errorf("failed to create session token: %w", err) } + err = storeSession(h.cfg, h.persister, token.UserID, rawToken, c, h.persister.GetConnection()) + if err != nil { + return fmt.Errorf("failed to store session in DB: %w", err) + } + c.Response().Header().Set("X-Session-Lifetime", fmt.Sprintf("%d", cookie.MaxAge)) if h.cfg.Session.EnableAuthTokenHeader { diff --git a/backend/handler/utils.go b/backend/handler/utils.go index 3c2cbeef8..09a74f703 100644 --- a/backend/handler/utils.go +++ b/backend/handler/utils.go @@ -1,7 +1,15 @@ package handler import ( + "fmt" + "github.com/gobuffalo/nulls" + "github.com/gobuffalo/pop/v6" + "github.com/gofrs/uuid" "github.com/labstack/echo/v4" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/teamhanko/hanko/backend/config" + "github.com/teamhanko/hanko/backend/persistence" + "github.com/teamhanko/hanko/backend/persistence/models" "net/http" ) @@ -21,3 +29,47 @@ func loadDto[I any](ctx echo.Context) (*I, error) { return &adminDto, nil } + +func storeSession(cfg *config.Config, persister persistence.Persister, userId uuid.UUID, rawToken jwt.Token, httpContext echo.Context, tx *pop.Connection) error { + activeSessions, err := persister.GetSessionPersisterWithConnection(tx).ListActive(userId) + if err != nil { + return fmt.Errorf("failed to list active sessions: %w", err) + } + + // remove all server side sessions that exceed the limit + if len(activeSessions) >= cfg.Session.Limit { + for i := cfg.Session.Limit - 1; i < len(activeSessions); i++ { + err = persister.GetSessionPersisterWithConnection(tx).Delete(activeSessions[i]) + if err != nil { + return fmt.Errorf("failed to remove latest session: %w", err) + } + } + } + + sessionID, _ := rawToken.Get("session_id") + + expirationTime := rawToken.Expiration() + sessionModel := models.Session{ + ID: uuid.FromStringOrNil(sessionID.(string)), + UserID: userId, + CreatedAt: rawToken.IssuedAt(), + UpdatedAt: rawToken.IssuedAt(), + ExpiresAt: &expirationTime, + LastUsed: rawToken.IssuedAt(), + } + + if cfg.Session.AcquireIPAddress { + sessionModel.IpAddress = nulls.NewString(httpContext.RealIP()) + } + + if cfg.Session.AcquireUserAgent { + sessionModel.UserAgent = nulls.NewString(httpContext.Request().UserAgent()) + } + + err = persister.GetSessionPersisterWithConnection(tx).Create(sessionModel) + if err != nil { + return fmt.Errorf("failed to store session: %w", err) + } + + return nil +} diff --git a/backend/handler/webauthn.go b/backend/handler/webauthn.go index 77d0d2994..16da7fae7 100644 --- a/backend/handler/webauthn.go +++ b/backend/handler/webauthn.go @@ -423,7 +423,7 @@ func (h *WebauthnHandler) FinishAuthentication(c echo.Context) error { emailJwt = dto.JwtFromEmailModel(e) } - token, _, err := h.sessionManager.GenerateJWT(webauthnUser.UserId, emailJwt) + token, rawToken, err := h.sessionManager.GenerateJWT(webauthnUser.UserId, emailJwt) if err != nil { return fmt.Errorf("failed to generate jwt: %w", err) } @@ -433,6 +433,11 @@ func (h *WebauthnHandler) FinishAuthentication(c echo.Context) error { return fmt.Errorf("failed to create session cookie: %w", err) } + err = storeSession(h.cfg, h.persister, webauthnUser.UserId, rawToken, c, tx) + if err != nil { + return fmt.Errorf("failed to store session in DB: %w", err) + } + c.Response().Header().Set("X-Session-Lifetime", fmt.Sprintf("%d", cookie.MaxAge)) if h.cfg.Session.EnableAuthTokenHeader { diff --git a/backend/test/config.go b/backend/test/config.go index 78e53e56b..7cb5c98db 100644 --- a/backend/test/config.go +++ b/backend/test/config.go @@ -37,6 +37,7 @@ var DefaultConfig = config.Config{ Cookie: config.Cookie{ SameSite: "none", }, + Limit: 5, }, Service: config.Service{ Name: "Test",