mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-26 13:27:57 +08:00
231 lines
8.6 KiB
Go
231 lines
8.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/echo/v4/middleware"
|
|
"github.com/sethvargo/go-limiter"
|
|
"github.com/sethvargo/go-limiter/httplimit"
|
|
"github.com/teamhanko/hanko/backend/v2/audit_log"
|
|
"github.com/teamhanko/hanko/backend/v2/config"
|
|
"github.com/teamhanko/hanko/backend/v2/crypto/jwk"
|
|
"github.com/teamhanko/hanko/backend/v2/dto"
|
|
"github.com/teamhanko/hanko/backend/v2/ee/saml"
|
|
"github.com/teamhanko/hanko/backend/v2/flow_api"
|
|
"github.com/teamhanko/hanko/backend/v2/flow_api/services"
|
|
"github.com/teamhanko/hanko/backend/v2/mail"
|
|
"github.com/teamhanko/hanko/backend/v2/mapper"
|
|
hankoMiddleware "github.com/teamhanko/hanko/backend/v2/middleware"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence"
|
|
"github.com/teamhanko/hanko/backend/v2/rate_limiter"
|
|
"github.com/teamhanko/hanko/backend/v2/session"
|
|
"github.com/teamhanko/hanko/backend/v2/template"
|
|
)
|
|
|
|
func NewPublicRouter(cfg *config.Config, persister persistence.Persister, prometheus echo.MiddlewareFunc, authenticatorMetadata mapper.AuthenticatorMetadata) *echo.Echo {
|
|
e := echo.New()
|
|
|
|
e.Renderer = template.NewTemplateRenderer()
|
|
|
|
e.Static("/flowpilot", "flow_api/static") // TODO: remove!
|
|
|
|
emailService, err := services.NewEmailService(*cfg)
|
|
passcodeService := services.NewPasscodeService(*cfg, *emailService, persister)
|
|
passwordService := services.NewPasswordService(persister)
|
|
webauthnService := services.NewWebauthnService(*cfg, persister)
|
|
|
|
jwkManager, err := jwk.NewDefaultManager(cfg.Secrets.Keys, persister.GetJwkPersister())
|
|
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))
|
|
}
|
|
|
|
var otpRateLimiter limiter.Store
|
|
var passcodeRateLimiter limiter.Store
|
|
var passwordRateLimiter limiter.Store
|
|
var tokenExchangeRateLimiter limiter.Store
|
|
if cfg.RateLimiter.Enabled {
|
|
otpRateLimiter = rate_limiter.NewRateLimiter(cfg.RateLimiter, cfg.RateLimiter.OTPLimits)
|
|
passcodeRateLimiter = rate_limiter.NewRateLimiter(cfg.RateLimiter, cfg.RateLimiter.PasscodeLimits)
|
|
passwordRateLimiter = rate_limiter.NewRateLimiter(cfg.RateLimiter, cfg.RateLimiter.PasswordLimits)
|
|
tokenExchangeRateLimiter = rate_limiter.NewRateLimiter(cfg.RateLimiter, cfg.RateLimiter.TokenLimits)
|
|
}
|
|
|
|
auditLogger := auditlog.NewLogger(persister, cfg.AuditLog)
|
|
|
|
samlService := saml.NewSamlService(cfg, persister)
|
|
|
|
flowAPIHandler := flow_api.FlowPilotHandler{
|
|
Persister: persister,
|
|
Cfg: *cfg,
|
|
PasscodeService: passcodeService,
|
|
PasswordService: passwordService,
|
|
WebauthnService: webauthnService,
|
|
SessionManager: sessionManager,
|
|
OTPRateLimiter: otpRateLimiter,
|
|
PasscodeRateLimiter: passcodeRateLimiter,
|
|
PasswordRateLimiter: passwordRateLimiter,
|
|
TokenExchangeRateLimiter: tokenExchangeRateLimiter,
|
|
AuthenticatorMetadata: authenticatorMetadata,
|
|
AuditLogger: auditLogger,
|
|
SamlService: samlService,
|
|
}
|
|
|
|
if cfg.Saml.Enabled {
|
|
saml.CreateSamlRoutes(e, sessionManager, auditLogger, samlService)
|
|
}
|
|
|
|
sessionMiddleware := hankoMiddleware.Session(cfg, persister, sessionManager)
|
|
|
|
webhookMiddleware := hankoMiddleware.WebhookMiddleware(cfg, jwkManager, persister)
|
|
|
|
e.POST("/registration", flowAPIHandler.RegistrationFlowHandler, webhookMiddleware)
|
|
e.POST("/login", flowAPIHandler.LoginFlowHandler, webhookMiddleware)
|
|
e.POST("/profile", flowAPIHandler.ProfileFlowHandler, webhookMiddleware)
|
|
|
|
if cfg.Saml.Enabled {
|
|
e.POST("/token_exchange", flowAPIHandler.TokenExchangeFlowHandler, webhookMiddleware)
|
|
}
|
|
|
|
e.HideBanner = true
|
|
g := e.Group("")
|
|
|
|
e.HTTPErrorHandler = dto.NewHTTPErrorHandler(dto.HTTPErrorHandlerConfig{Debug: false, Logger: e.Logger})
|
|
e.Use(middleware.RequestID())
|
|
if cfg.Log.LogHealthAndMetrics {
|
|
e.Use(hankoMiddleware.GetLoggerMiddleware())
|
|
} else {
|
|
g.Use(hankoMiddleware.GetLoggerMiddleware())
|
|
}
|
|
|
|
exposeHeader := []string{
|
|
httplimit.HeaderRetryAfter,
|
|
httplimit.HeaderRateLimitLimit,
|
|
httplimit.HeaderRateLimitRemaining,
|
|
httplimit.HeaderRateLimitReset,
|
|
"X-Session-Lifetime",
|
|
"X-Session-Retention",
|
|
}
|
|
|
|
if cfg.Session.EnableAuthTokenHeader {
|
|
exposeHeader = append(exposeHeader, "X-Auth-Token")
|
|
}
|
|
|
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
UnsafeWildcardOriginWithAllowCredentials: cfg.Server.Public.Cors.UnsafeWildcardOriginAllowed,
|
|
AllowOrigins: cfg.Server.Public.Cors.AllowOrigins,
|
|
ExposeHeaders: exposeHeader,
|
|
AllowCredentials: true,
|
|
// Based on: Chromium (starting in v76) caps at 2 hours (7200 seconds).
|
|
MaxAge: 7200,
|
|
}))
|
|
|
|
if prometheus != nil {
|
|
e.Use(prometheus)
|
|
}
|
|
|
|
e.Validator = dto.NewCustomValidator()
|
|
|
|
mailer, err := mail.NewMailer(cfg.EmailDelivery.SMTP)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create mailer: %w", err))
|
|
}
|
|
|
|
if !cfg.MFA.Enabled && cfg.Password.Enabled {
|
|
passwordHandler := NewPasswordHandler(persister, sessionManager, cfg, auditLogger)
|
|
|
|
password := g.Group("/password")
|
|
password.PUT("", passwordHandler.Set, sessionMiddleware)
|
|
password.POST("/login", passwordHandler.Login)
|
|
}
|
|
|
|
userHandler := NewUserHandler(cfg, persister, sessionManager, auditLogger)
|
|
statusHandler := NewStatusHandler(persister)
|
|
|
|
e.GET("/", statusHandler.Status)
|
|
g.GET("/me", userHandler.Me, sessionMiddleware)
|
|
|
|
user := g.Group("/users", webhookMiddleware)
|
|
user.POST("", userHandler.Create)
|
|
user.GET("/:id", userHandler.Get, sessionMiddleware)
|
|
|
|
g.POST("/user", userHandler.GetUserIdByEmail)
|
|
g.POST("/logout", userHandler.Logout, sessionMiddleware)
|
|
|
|
if cfg.Account.AllowDeletion {
|
|
g.DELETE("/user", userHandler.Delete, sessionMiddleware, webhookMiddleware)
|
|
}
|
|
|
|
healthHandler := NewHealthHandler()
|
|
|
|
health := e.Group("/health")
|
|
health.GET("/alive", healthHandler.Alive)
|
|
health.GET("/ready", healthHandler.Ready)
|
|
|
|
wellKnownHandler, err := NewWellKnownHandler(*cfg, jwkManager)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create well-known handler: %w", err))
|
|
}
|
|
wellKnown := g.Group("/.well-known")
|
|
wellKnown.GET("/jwks.json", wellKnownHandler.GetPublicKeys)
|
|
wellKnown.GET("/config", wellKnownHandler.GetConfig)
|
|
|
|
emailHandler := NewEmailHandler(cfg, persister, auditLogger)
|
|
|
|
if cfg.Passkey.Enabled {
|
|
webauthnHandler, err := NewWebauthnHandler(cfg, persister, sessionManager, auditLogger, authenticatorMetadata)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create public webauthn handler: %w", err))
|
|
}
|
|
webauthn := g.Group("/webauthn")
|
|
webauthnRegistration := webauthn.Group("/registration", sessionMiddleware)
|
|
webauthnRegistration.POST("/initialize", webauthnHandler.BeginRegistration)
|
|
webauthnRegistration.POST("/finalize", webauthnHandler.FinishRegistration)
|
|
|
|
webauthnLogin := webauthn.Group("/login")
|
|
webauthnLogin.POST("/initialize", webauthnHandler.BeginAuthentication)
|
|
webauthnLogin.POST("/finalize", webauthnHandler.FinishAuthentication)
|
|
|
|
webauthnCredentials := webauthn.Group("/credentials", sessionMiddleware)
|
|
webauthnCredentials.GET("", webauthnHandler.ListCredentials)
|
|
webauthnCredentials.PATCH("/:id", webauthnHandler.UpdateCredential)
|
|
webauthnCredentials.DELETE("/:id", webauthnHandler.DeleteCredential)
|
|
}
|
|
|
|
if !cfg.MFA.Enabled && cfg.Email.Enabled && cfg.Email.UseForAuthentication {
|
|
passcodeHandler, err := NewPasscodeHandler(cfg, persister, sessionManager, mailer, auditLogger)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create public passcode handler: %w", err))
|
|
}
|
|
passcode := g.Group("/passcode")
|
|
passcodeLogin := passcode.Group("/login", webhookMiddleware)
|
|
passcodeLogin.POST("/initialize", passcodeHandler.Init)
|
|
passcodeLogin.POST("/finalize", passcodeHandler.Finish)
|
|
}
|
|
|
|
email := g.Group("/emails", sessionMiddleware, webhookMiddleware)
|
|
email.GET("", emailHandler.List)
|
|
email.POST("", emailHandler.Create)
|
|
email.DELETE("/:id", emailHandler.Delete)
|
|
email.POST("/:id/set_primary", emailHandler.SetPrimaryEmail)
|
|
|
|
thirdPartyHandler := NewThirdPartyHandler(cfg, persister, sessionManager, auditLogger)
|
|
thirdparty := g.Group("thirdparty")
|
|
thirdparty.GET("/auth", thirdPartyHandler.Auth)
|
|
thirdparty.GET("/callback", thirdPartyHandler.Callback, webhookMiddleware)
|
|
thirdparty.POST("/callback", thirdPartyHandler.CallbackPost, webhookMiddleware)
|
|
|
|
tokenHandler := NewTokenHandler(cfg, persister, sessionManager, auditLogger)
|
|
g.POST("/token", tokenHandler.Validate)
|
|
|
|
sessionHandler := NewSessionHandler(persister, sessionManager, *cfg)
|
|
sessions := g.Group("sessions")
|
|
sessions.GET("/validate", sessionHandler.ValidateSession)
|
|
sessions.POST("/validate", sessionHandler.ValidateSessionFromBody)
|
|
|
|
return e
|
|
}
|