// Copyright (c) 2025 Nikolai Papin // // This file is part of Easywish // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See // the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package controllers import ( errs "easywish/internal/errors" "easywish/internal/middleware" "easywish/internal/models" "easywish/internal/services" "easywish/internal/utils" "easywish/internal/utils/enums" "errors" "net/http" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type AuthController interface { RegistrationBegin(c *gin.Context) RegistrationComplete(c *gin.Context) Login(c *gin.Context) Refresh(c *gin.Context) PasswordResetBegin(c *gin.Context) PasswordResetComplete(c *gin.Context) Router } type authControllerImpl struct { log *zap.Logger auth services.AuthService } func NewAuthController(_log *zap.Logger, _auth services.AuthService) AuthController { return &authControllerImpl{log: _log, auth: _auth} } // @Summary Acquire tokens via login credentials (and 2FA code if needed) // @Tags Auth // @Accept json // @Produce json // @Param request body models.LoginRequest true " " // @Success 200 {object} models.LoginResponse " " // @Failure 403 "Invalid login credentials" // @Router /auth/login [post] func (a *authControllerImpl) Login(c *gin.Context) { request, ok := utils.GetRequest[models.LoginRequest](c) if !ok { c.Status(http.StatusBadRequest) return } response, err := a.auth.Login(request.Body) if err != nil { if errors.Is(err, errs.ErrForbidden) { c.Status(http.StatusForbidden) } else { c.Status(http.StatusInternalServerError) } return } c.JSON(http.StatusOK, response) return } // @Summary Request password reset email // @Tags Auth // @Accept json // @Produce json // @Param request body models.PasswordResetBeginRequest true " " // @Router /auth/passwordResetBegin [post] // @Success 200 "Reset code sent to the email if it is attached to an account" // @Failure 429 "Too many recent requests for this email" func (a *authControllerImpl) PasswordResetBegin(c *gin.Context) { c.Status(http.StatusNotImplemented) } // @Summary Complete password reset via email code // @Tags Auth // @Accept json // @Produce json // @Param request body models.PasswordResetCompleteRequest true " " // @Router /auth/passwordResetComplete [post] // @Success 200 {object} models.PasswordResetCompleteResponse " " // @Success 403 "Wrong verification code or username" func (a *authControllerImpl) PasswordResetComplete(c *gin.Context) { c.Status(http.StatusNotImplemented) } // @Summary Receive new tokens via refresh token // @Tags Auth // @Accept json // @Produce json // @Param request body models.RefreshRequest true " " // @Router /auth/refresh [post] // @Success 200 {object} models.RefreshResponse " " // @Failure 401 "Invalid refresh token" func (a *authControllerImpl) Refresh(c *gin.Context) { c.Status(http.StatusNotImplemented) } // @Summary Register an account // @Tags Auth // @Accept json // @Produce json // @Param request body models.RegistrationBeginRequest true " " // @Success 200 "Account is created and awaiting verification" // @Failure 409 "Username or email is already taken" // @Failure 429 "Too many recent registration attempts for this email" // @Router /auth/registrationBegin [post] func (a *authControllerImpl) RegistrationBegin(c *gin.Context) { request, ok := utils.GetRequest[models.RegistrationBeginRequest](c) if !ok { c.Status(http.StatusBadRequest) return } _, err := a.auth.RegistrationBegin(request.Body) if err != nil { if errors.Is(err, errs.ErrUsernameTaken) || errors.Is(err, errs.ErrEmailTaken) { c.Status(http.StatusConflict) } else { c.Status(http.StatusInternalServerError) } return } c.Status(http.StatusOK) return } // @Summary Confirm with code, finish creating the account // @Tags Auth // @Accept json // @Produce json // @Param request body models.RegistrationCompleteRequest true " " // @Success 200 {object} models.RegistrationCompleteResponse " " // @Failure 403 "Invalid email or verification code" // @Router /auth/registrationComplete [post] func (a *authControllerImpl) RegistrationComplete(c *gin.Context) { request, ok := utils.GetRequest[models.RegistrationCompleteRequest](c) if !ok { c.Status(http.StatusBadRequest) return } response, err := a.auth.RegistrationComplete(request.Body) if err != nil { if errors.Is(err, errs.ErrForbidden) { c.Status(http.StatusForbidden) } else if errors.Is(err, errs.ErrUnauthorized) { c.Status(http.StatusUnauthorized) } else { c.Status(http.StatusInternalServerError) } return } c.JSON(http.StatusOK, response) } func (a *authControllerImpl) RegisterRoutes(group *gin.RouterGroup) { group.POST("/registrationBegin", middleware.RequestMiddleware[models.RegistrationBeginRequest](enums.GuestRole), a.RegistrationBegin) group.POST("/registrationComplete", middleware.RequestMiddleware[models.RegistrationCompleteRequest](enums.GuestRole), a.RegistrationComplete) group.POST("/login", middleware.RequestMiddleware[models.LoginRequest](enums.GuestRole), a.Login) group.POST("/refresh", middleware.RequestMiddleware[models.RegistrationBeginRequest](enums.UserRole), a.Refresh) group.POST("/passwordResetBegin", middleware.RequestMiddleware[models.PasswordResetBeginRequest](enums.GuestRole), a.PasswordResetBegin) group.POST("/passwordResetComplete", middleware.RequestMiddleware[models.RegistrationBeginRequest](enums.GuestRole), a.PasswordResetComplete) }