Skip to content

Commit

Permalink
feat: add session creation endpoint (#1969)
Browse files Browse the repository at this point in the history
  • Loading branch information
FreddyDevelop authored Nov 12, 2024
1 parent b0872b8 commit b643256
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 0 deletions.
11 changes: 11 additions & 0 deletions backend/dto/admin/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package admin

type CreateSessionTokenDto struct {
UserID string `json:"user_id" validate:"required,uuid4"`
UserAgent string `json:"user_agent"`
IpAddress string `json:"ip_address" validate:"omitempty,ip"`
}

type CreateSessionTokenResponse struct {
SessionToken string `json:"session_token"`
}
2 changes: 2 additions & 0 deletions backend/dto/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func (cv *CustomValidator) Validate(i interface{}) error {
vErrs[i] = fmt.Sprintf("%s entries are not unique", err.Field())
case "hanko_event":
vErrs[i] = fmt.Sprintf("%s in %s is not a valid webhook event", err.Value(), err.Field())
case "ip":
vErrs[i] = fmt.Sprintf("%s must be a valid ip address (v4 or v6)", err.Field())
default:
vErrs[i] = fmt.Sprintf("something wrong on %s; %s", err.Field(), err.Tag())
}
Expand Down
11 changes: 11 additions & 0 deletions backend/handler/admin_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
auditlog "github.com/teamhanko/hanko/backend/audit_log"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/crypto/jwk"
"github.com/teamhanko/hanko/backend/dto"
hankoMiddleware "github.com/teamhanko/hanko/backend/middleware"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/session"
"github.com/teamhanko/hanko/backend/template"
)

Expand Down Expand Up @@ -48,8 +50,13 @@ func NewAdminRouter(cfg *config.Config, persister persistence.Persister, prometh
if err != nil {
panic(fmt.Errorf("failed to create jwk manager: %w", err))
}
sessionManager, err := session.NewManager(jwkManager, *cfg)
if err != nil {
panic(fmt.Errorf("failed to create session generator: %w", err))
}

webhookMiddleware := hankoMiddleware.WebhookMiddleware(cfg, jwkManager, persister)
auditLogger := auditlog.NewLogger(persister, cfg.AuditLog)

userHandler := NewUserHandlerAdmin(persister)
emailHandler := NewEmailAdminHandler(cfg, persister)
Expand Down Expand Up @@ -80,5 +87,9 @@ func NewAdminRouter(cfg *config.Config, persister persistence.Persister, prometh
webhooks.DELETE("/:id", webhookHandler.Delete)
webhooks.PUT("/:id", webhookHandler.Update)

sessionsHandler := NewSessionAdminHandler(cfg, persister, sessionManager, auditLogger)
sessions := g.Group("/sessions")
sessions.POST("", sessionsHandler.Generate)

return e
}
113 changes: 113 additions & 0 deletions backend/handler/session_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package handler

import (
"fmt"
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"
auditlog "github.com/teamhanko/hanko/backend/audit_log"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/dto"
"github.com/teamhanko/hanko/backend/dto/admin"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/persistence/models"
"github.com/teamhanko/hanko/backend/session"
"net/http"
)

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")
}

var emailDTO *dto.EmailJwt
if email := user.Emails.GetPrimary(); email != nil {
emailDTO = dto.JwtFromEmailModel(email)
}

encodedToken, rawToken, err := h.sessionManger.GenerateJWT(userID, emailDTO)
if err != nil {
return fmt.Errorf("failed to generate JWT: %w", err)
}

if h.cfg.Session.ServerSide.Enabled {
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.ServerSide.Limit {
for i := h.cfg.Session.ServerSide.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,
UserAgent: body.UserAgent,
IpAddress: body.IpAddress,
CreatedAt: rawToken.IssuedAt(),
UpdatedAt: rawToken.IssuedAt(),
ExpiresAt: &expirationTime,
LastUsed: rawToken.IssuedAt(),
}

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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
change_column("sessions", "user_agent", "string", {"null": false})
change_column("sessions", "ip_address", "string", {"null": false})
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
change_column("sessions", "user_agent", "string", {"null": true})
change_column("sessions", "ip_address", "string", {"null": true})

0 comments on commit b643256

Please sign in to comment.