Files
fm_be/internal/app/api/routes_auth.go
2026-07-16 22:16:45 +07:00

143 lines
6.4 KiB
Go

package api
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
samlauth "wucher/internal/auth"
"wucher/internal/service"
"wucher/internal/transport/http/handlers"
"wucher/internal/transport/http/middleware"
)
func registerAuthRoutes(v1 fiber.Router, authHandler *handlers.AuthHandler, authSvc *service.AuthService, samlHandler *samlauth.Handler) {
auth := v1.Group("/auth")
auth.Post("/register", authHandler.Register)
auth.Post("/invite", middleware.AuthRequired(authSvc), middleware.PermissionRequired(authSvc, "user.create"), authHandler.Invite)
auth.Post("/invite/resend-set-password", middleware.AuthRequired(authSvc), middleware.PermissionRequired(authSvc, "user.update"), forgotPasswordIPLimiter(authSvc), authHandler.ResendSetPasswordInvite)
auth.Post("/set-password", authHandler.SetPassword)
auth.Post("/forgot-password", forgotPasswordIPLimiter(authSvc), authHandler.ForgotPassword)
auth.Post("/reset-password", authHandler.ResetPassword)
auth.Post("/pin/setup", middleware.AuthRequired(authSvc), authHandler.SetupSecurityPIN)
auth.Post("/pin/verify", middleware.AuthRequired(authSvc), authHandler.VerifySecurityPIN)
auth.Post("/pin/change", middleware.AuthRequired(authSvc), authHandler.ChangeSecurityPIN)
auth.Post("/pin/request-reset", middleware.AuthRequired(authSvc), forgotPasswordIPLimiter(authSvc), authHandler.RequestSecurityPINReset)
auth.Post("/pin/forgot", forgotPasswordIPLimiter(authSvc), authHandler.ForgotSecurityPIN)
auth.Post("/pin/reset", authHandler.ResetSecurityPIN)
auth.Post("/login", loginIPLimiter(authSvc), authHandler.Login)
auth.Post("/exchange", authHandler.Exchange)
auth.Post("/webauthn/register/begin", middleware.AuthRequired(authSvc), authHandler.WebAuthnRegisterBegin)
auth.Post("/webauthn/register/finish", middleware.AuthRequired(authSvc), authHandler.WebAuthnRegisterFinish)
auth.Post("/webauthn/login/begin", authHandler.WebAuthnLoginBegin)
auth.Post("/webauthn/login/finish", authHandler.WebAuthnLoginFinish)
auth.Post("/webauthn/reset-setup", middleware.AuthRequired(authSvc), authHandler.WebAuthnResetSetup)
auth.Post("/refresh", authHandler.Refresh)
auth.Post("/logout", authHandler.Logout)
auth.Get("/sessions", middleware.AuthRequired(authSvc), authHandler.Sessions)
auth.Delete("/sessions/:session_id", middleware.AuthRequired(authSvc), authHandler.DeleteSession)
auth.Get("/me", middleware.AuthRequired(authSvc), authHandler.Me)
auth.Patch("/me/timezone", middleware.AuthRequired(authSvc), authHandler.UpdateTimezone)
auth.Get("/verify-email", authHandler.VerifyEmail)
if samlHandler != nil {
auth.Get("/microsoft/login", samlHandler.HandleLogin)
auth.Get("/microsoft/metadata", samlHandler.HandleMetadata)
} else {
auth.Get("/microsoft/login", authHandler.MicrosoftLogin)
}
auth.Get("/microsoft/login-check", authHandler.MicrosoftLoginCheck)
auth.Get("/microsoft/link", middleware.AuthRequired(authSvc), authHandler.MicrosoftLink)
auth.Get("/microsoft/reauth", middleware.AuthRequired(authSvc), authHandler.MicrosoftReauth)
if samlHandler != nil {
auth.Get("/microsoft/callback", samlHandler.HandleSAMLCallback)
auth.Post("/microsoft/callback", samlHandler.HandleSAMLCallback)
auth.Get("/microsoft/logout", func(c *fiber.Ctx) error {
if c.Query("SAMLRequest") != "" {
return samlHandler.HandleSAMLLogoutRequest(c)
}
if c.Query("SAMLResponse") != "" {
return samlHandler.HandleSAMLLogoutResponse(c)
}
return samlHandler.HandleBrowserLocalLogout(c)
})
auth.Post("/microsoft/logout", func(c *fiber.Ctx) error {
if c.FormValue("SAMLRequest") != "" {
return samlHandler.HandleSAMLLogoutRequest(c)
}
if c.FormValue("SAMLResponse") != "" {
return samlHandler.HandleSAMLLogoutResponse(c)
}
return samlHandler.HandleBrowserLocalLogout(c)
})
} else {
auth.Get("/microsoft/callback", authHandler.MicrosoftCallback)
auth.Post("/microsoft/logout", middleware.AuthRequired(authSvc), authHandler.MicrosoftLogout)
}
// OIDC front-channel logout: invoked by Microsoft Entra in a hidden iframe when the
// user signs out at the IdP. Must be public (the IdP has no app credentials) and
// available regardless of the active Microsoft auth mode.
auth.Get("/microsoft/front-channel-logout", authHandler.MicrosoftFrontChannelLogout)
auth.Post("/microsoft/front-channel-logout", authHandler.MicrosoftFrontChannelLogout)
auth.Post("/microsoft/refresh", middleware.AuthRequired(authSvc), authHandler.MicrosoftRefresh)
auth.Post("/sso/link", middleware.AuthRequired(authSvc), authHandler.SSOLink)
auth.Delete("/sso/unlink", middleware.AuthRequired(authSvc), authHandler.SSOUnlink)
auth.Post("/totp/setup", middleware.AuthRequired(authSvc), authHandler.TOTPSetup)
auth.Post("/totp/confirm", middleware.AuthRequired(authSvc), authHandler.TOTPConfirm)
auth.Post("/totp/verify", authHandler.TOTPVerify)
auth.Post("/email-otp/send", authHandler.SendEmailOTP)
auth.Post("/email-otp/verify", authHandler.VerifyEmailOTP)
auth.Post("/totp/disable", middleware.AuthRequired(authSvc), authHandler.TOTPDisable)
auth.Post("/totp/reset-setup", middleware.AuthRequired(authSvc), authHandler.TOTPResetSetup)
}
func forgotPasswordIPLimiter(authSvc *service.AuthService) fiber.Handler {
window := authSvc.ForgotPasswordIPRateWindow()
return limiter.New(limiter.Config{
Max: authSvc.ForgotPasswordIPRateMax(),
Expiration: window,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
LimitReached: func(c *fiber.Ctx) error {
retryAfter := int(window.Seconds())
if retryAfter < 1 {
retryAfter = 1
}
c.Set(fiber.HeaderRetryAfter, strconv.Itoa(retryAfter))
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"errors": []fiber.Map{{
"status": "429",
"title": "Too Many Requests",
"detail": "rate limit exceeded",
}},
})
},
})
}
func loginIPLimiter(authSvc *service.AuthService) fiber.Handler {
window := authSvc.LoginIPRateWindow()
return limiter.New(limiter.Config{
Max: authSvc.LoginIPRateMax(),
Expiration: window,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
LimitReached: func(c *fiber.Ctx) error {
retryAfter := int(window.Seconds())
if retryAfter < 1 {
retryAfter = 1
}
c.Set(fiber.HeaderRetryAfter, strconv.Itoa(retryAfter))
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"errors": []fiber.Map{{
"status": "429",
"title": "Too Many Requests",
"detail": "rate limit exceeded",
}},
})
},
})
}