package handlers import ( "context" "encoding/json" "fmt" "net/http" "github.com/labstack/echo/v5" "trankilou.fr/lassistanoque/backend/internal/domain" "trankilou.fr/lassistanoque/backend/internal/service/auth" ) func NewAuthGroup(prefix string, e *echo.Group, service *auth.Service) *echo.Group { authHandler := &AuthHandler{ authService: service, } auth := e.Group(prefix) auth.POST("/login", authHandler.Login) auth.GET("/oidc/providers", authHandler.OIDCProviders) auth.GET("/oidc/login", authHandler.OIDCLogin) auth.GET("/oidc/callback", authHandler.OIDCCallback) auth.POST("/register", authHandler.Register) return auth } type AuthHandler struct { authService *auth.Service } type loginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } type loginResponse struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` ExpiresAt string `json:"expiresAt"` User *domain.User `json:"user"` } func (h AuthHandler) Login(c *echo.Context) error { var req loginRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } if req.Email == "" || req.Password == "" { return echo.NewHTTPError(http.StatusBadRequest, "email and password are required") } creds := auth.Credentials{ Method: "password", Email: req.Email, Password: req.Password, } session, err := h.authService.Login(context.Background(), creds) if err != nil { c.Logger().Error(fmt.Sprintf("error while login: %s", err)) return echo.NewHTTPError(http.StatusUnauthorized, "login error") } resp := loginResponse{ AccessToken: session.AccessToken, RefreshToken: session.RefreshToken, ExpiresAt: session.ExpiresAt.Format("2006-01-02T15:04:05Z07:00"), } resp.User = session.User return c.JSON(http.StatusOK, resp) } // oidcRedirectURI reconstruit l'URL de callback du serveur à partir de la // requête. Elle doit être identique entre /oidc/login et /oidc/callback. func oidcRedirectURI(c *echo.Context) string { scheme := "http" if proto := c.Request().Header.Get("X-Forwarded-Proto"); proto != "" { scheme = proto } else if c.Request().TLS != nil { scheme = "https" } host := c.Request().Header.Get("X-Forwarded-Host") if host == "" { host = c.Request().Host } return scheme + "://" + host + "/api/auth/oidc/callback" } func (h AuthHandler) OIDCProviders(c *echo.Context) error { providers, err := h.authService.ListOIDCProviders() if err != nil { c.Logger().Error(fmt.Sprintf("error listing oidc providers: %s", err)) return echo.NewHTTPError(http.StatusInternalServerError, "error listing oidc providers") } return c.JSON(http.StatusOK, providers) } func (h AuthHandler) OIDCLogin(c *echo.Context) error { providerID := c.QueryParam("provider") if providerID == "" { return echo.NewHTTPError(http.StatusBadRequest, "provider query parameter is required") } authorizeURL, err := h.authService.OIDCAuthorizeURL(context.Background(), providerID, oidcRedirectURI(c)) if err != nil { c.Logger().Error(fmt.Sprintf("error building oidc authorize url: %s", err)) return c.Redirect(http.StatusFound, "/login?error=oidc") } return c.Redirect(http.StatusFound, authorizeURL) } func (h AuthHandler) OIDCCallback(c *echo.Context) error { if errParam := c.QueryParam("error"); errParam != "" { c.Logger().Error(fmt.Sprintf("oidc provider returned an error: %s", errParam)) return c.Redirect(http.StatusFound, "/login?error=oidc") } code := c.QueryParam("code") state := c.QueryParam("state") if code == "" || state == "" { return echo.NewHTTPError(http.StatusBadRequest, "code and state are required") } session, err := h.authService.OIDCCallback(context.Background(), state, code, oidcRedirectURI(c)) if err != nil { c.Logger().Error(fmt.Sprintf("error while oidc callback: %s", err)) return c.Redirect(http.StatusFound, "/login?error=oidc") } // L'application SPA est servie par ce même serveur (même origine) et lit // ses tokens dans localStorage: on les dépose ici puis on redirige vers // l'application, sans jamais les faire transiter dans une URL. page := fmt.Sprintf(` Connexion en cours... `, session.AccessToken, session.RefreshToken) return c.Blob(http.StatusOK, "text/html; charset=utf-8", []byte(page)) } func (h AuthHandler) Register(c *echo.Context) error { reader := c.Request().Body var registration auth.Registration decoder := json.NewDecoder(reader) err := decoder.Decode(®istration) if err != nil { return c.String(http.StatusBadRequest, "malformatted registration") } user, err := h.authService.Register(registration) if err != nil { return c.String(http.StatusBadRequest, err.Error()) } return c.JSON(http.StatusCreated, user) }