Files
hanko/backend/handler/session.go
Frederic Jahn 9dbc62524a feat: Server side sessions (#1673)
* feat: add server side sessions

* feat: add lastUsed & admin endpoint

* feat: add session list to elements

* fix: fix public session endpoint

* chore: only store session info when enabled

* build: update go mod

* feat: add translations

* test: fix tests

* feat: change path

* feat: return userID on session validation endpoint

* feat: move all session endpoints to public router

* fix: add missing translation

* fix: add missing structs

* chore: align session persister with other persisters

* fix: use correct translation label

* chore: add db validator to session model

* feat: create server side session from cmd

* fix: fix review findings
2024-10-15 11:36:32 +02:00

149 lines
3.8 KiB
Go

package handler
import (
"fmt"
"github.com/gofrs/uuid"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/dto"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/session"
"net/http"
"time"
)
type SessionHandler struct {
persister persistence.Persister
sessionManager session.Manager
cfg config.Config
}
func NewSessionHandler(persister persistence.Persister, sessionManager session.Manager, cfg config.Config) *SessionHandler {
return &SessionHandler{
persister: persister,
sessionManager: sessionManager,
cfg: cfg,
}
}
func (h *SessionHandler) ValidateSession(c echo.Context) error {
lookup := fmt.Sprintf("header:Authorization:Bearer,cookie:%s", h.cfg.Session.Cookie.GetName())
extractors, err := echojwt.CreateExtractors(lookup)
if err != nil {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
var token jwt.Token
for _, extractor := range extractors {
auths, extractorErr := extractor(c)
if extractorErr != nil {
continue
}
for _, auth := range auths {
t, tokenErr := h.sessionManager.Verify(auth)
if tokenErr != nil {
continue
}
if h.cfg.Session.ServerSide.Enabled {
// check that the session id is stored in the database
sessionId, ok := t.Get("session_id")
if !ok {
continue
}
sessionID, err := uuid.FromString(sessionId.(string))
if err != nil {
continue
}
sessionModel, err := h.persister.GetSessionPersister().Get(sessionID)
if err != nil {
return fmt.Errorf("failed to get session from database: %w", err)
}
if sessionModel == nil {
continue
}
// Update lastUsed field
sessionModel.LastUsed = time.Now().UTC()
err = h.persister.GetSessionPersister().Update(*sessionModel)
if err != nil {
return dto.ToHttpError(err)
}
}
token = t
break
}
}
if token != nil {
expirationTime := token.Expiration()
userID := uuid.FromStringOrNil(token.Subject())
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{
IsValid: true,
ExpirationTime: &expirationTime,
UserID: &userID,
})
} else {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
}
func (h *SessionHandler) ValidateSessionFromBody(c echo.Context) error {
var request dto.ValidateSessionRequest
err := (&echo.DefaultBinder{}).BindBody(c, &request)
if err != nil {
return dto.ToHttpError(err)
}
err = c.Validate(request)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
token, err := h.sessionManager.Verify(request.SessionToken)
if err != nil {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
if h.cfg.Session.ServerSide.Enabled {
// check that the session id is stored in the database
sessionId, ok := token.Get("session_id")
if !ok {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
sessionID, err := uuid.FromString(sessionId.(string))
if err != nil {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
sessionModel, err := h.persister.GetSessionPersister().Get(sessionID)
if err != nil {
return dto.ToHttpError(err)
}
if sessionModel == nil {
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{IsValid: false})
}
// update lastUsed field
sessionModel.LastUsed = time.Now().UTC()
err = h.persister.GetSessionPersister().Update(*sessionModel)
if err != nil {
return dto.ToHttpError(err)
}
}
expirationTime := token.Expiration()
userID := uuid.FromStringOrNil(token.Subject())
return c.JSON(http.StatusOK, dto.ValidateSessionResponse{
IsValid: true,
ExpirationTime: &expirationTime,
UserID: &userID,
})
}