143 lines
6.4 KiB
Go
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",
|
|
}},
|
|
})
|
|
},
|
|
})
|
|
}
|