package handler import ( "fmt" "net/http" "github.com/gobuffalo/nulls" "github.com/gofrs/uuid" "github.com/labstack/echo/v4" "github.com/pkg/errors" auditlog "github.com/teamhanko/hanko/backend/v2/audit_log" "github.com/teamhanko/hanko/backend/v2/config" "github.com/teamhanko/hanko/backend/v2/dto" "github.com/teamhanko/hanko/backend/v2/dto/admin" "github.com/teamhanko/hanko/backend/v2/persistence" "github.com/teamhanko/hanko/backend/v2/persistence/models" "github.com/teamhanko/hanko/backend/v2/session" ) type SessionAdminHandler struct { cfg *config.Config persister persistence.Persister sessionManger session.Manager auditLogger auditlog.Logger } func NewSessionAdminHandler(cfg *config.Config, persister persistence.Persister, sessionManager session.Manager, auditLogger auditlog.Logger) SessionAdminHandler { return SessionAdminHandler{ cfg: cfg, persister: persister, sessionManger: sessionManager, auditLogger: auditLogger, } } func (h *SessionAdminHandler) Generate(ctx echo.Context) error { var body admin.CreateSessionTokenDto if err := (&echo.DefaultBinder{}).BindBody(ctx, &body); err != nil { return dto.ToHttpError(err) } if err := ctx.Validate(body); err != nil { return dto.ToHttpError(err) } userID, err := uuid.FromString(body.UserID) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "failed to parse userId as uuid").SetInternal(err) } user, err := h.persister.GetUserPersister().Get(userID) if err != nil { return err } if user == nil { return echo.NewHTTPError(http.StatusNotFound, "user not found") } encodedToken, rawToken, err := h.sessionManger.GenerateJWT(dto.UserJWTFromUserModel(user)) if err != nil { return fmt.Errorf("failed to generate JWT: %w", err) } activeSessions, err := h.persister.GetSessionPersister().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) >= h.cfg.Session.Limit { for i := h.cfg.Session.Limit - 1; i < len(activeSessions); i++ { err = h.persister.GetSessionPersister().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 len(body.UserAgent) > 0 { sessionModel.UserAgent = nulls.NewString(body.UserAgent) } if len(body.IpAddress) > 0 { sessionModel.IpAddress = nulls.NewString(body.IpAddress) } err = h.persister.GetSessionPersister().Create(sessionModel) if err != nil { return fmt.Errorf("failed to store session: %w", err) } response := admin.CreateSessionTokenResponse{ SessionToken: encodedToken, } err = h.auditLogger.Create(ctx, models.AuditLogLoginSuccess, user, nil, auditlog.Detail("api", "admin")) if err != nil { return fmt.Errorf("could not create audit log: %w", err) } return ctx.JSON(http.StatusOK, response) } func (h *SessionAdminHandler) List(ctx echo.Context) error { listDto, err := loadDto[admin.ListSessionsRequestDto](ctx) if err != nil { return err } userID, err := uuid.FromString(listDto.UserID) if err != nil { return fmt.Errorf(parseUserUuidFailureMessage, err) } user, err := h.persister.GetUserPersister().Get(userID) if err != nil { return err } if user == nil { return echo.NewHTTPError(http.StatusNotFound) } sessions, err := h.persister.GetSessionPersister().ListActive(userID) if err != nil { return err } return ctx.JSON(http.StatusOK, sessions) } func (h *SessionAdminHandler) Delete(ctx echo.Context) error { deleteDto, err := loadDto[admin.DeleteSessionRequestDto](ctx) if err != nil { return err } userID, err := uuid.FromString(deleteDto.UserID) if err != nil { return fmt.Errorf(parseUserUuidFailureMessage, err) } user, err := h.persister.GetUserPersister().Get(userID) if err != nil { return err } if user == nil { return echo.NewHTTPError(http.StatusNotFound) } sessionID, err := uuid.FromString(deleteDto.SessionID) if err != nil { return fmt.Errorf("failed to parse session_id as uuid: %s", err) } sessionModel, err := h.persister.GetSessionPersister().Get(sessionID) if err != nil { return err } if sessionModel == nil { return echo.NewHTTPError(http.StatusNotFound) } else if sessionModel.UserID != userID { return echo.NewHTTPError(http.StatusNotFound).SetInternal(errors.New("session does not belong to user")) } err = h.persister.GetSessionPersister().Delete(*sessionModel) if err != nil { return err } return ctx.NoContent(http.StatusNoContent) }