mirror of
				https://github.com/h44z/wg-portal.git
				synced 2025-10-31 22:46:18 +00:00 
			
		
		
		
	
		
			
	
	
		
			266 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			266 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | package server | ||
|  | 
 | ||
|  | // go get -u github.com/swaggo/swag/cmd/swag | ||
|  | // run: swag init --parseDependency --parseInternal --generalInfo api.go | ||
|  | // in the internal/server folder | ||
|  | import ( | ||
|  | 	"encoding/json" | ||
|  | 	"net/http" | ||
|  | 	"strings" | ||
|  | 
 | ||
|  | 	jsonpatch "github.com/evanphx/json-patch" | ||
|  | 	"github.com/gin-gonic/gin" | ||
|  | 	"github.com/h44z/wg-portal/internal/users" | ||
|  | ) | ||
|  | 
 | ||
|  | // @title WireGuard Portal API | ||
|  | // @version 1.0 | ||
|  | // @description WireGuard Portal API for managing users and peers. | ||
|  | 
 | ||
|  | // @license.name MIT | ||
|  | // @license.url https://github.com/h44z/wg-portal/blob/master/LICENSE.txt | ||
|  | 
 | ||
|  | // @securityDefinitions.basic ApiBasicAuth | ||
|  | // @in header | ||
|  | // @name Authorization | ||
|  | 
 | ||
|  | // @BasePath /api/v1 | ||
|  | 
 | ||
|  | // ApiServer is a simple wrapper struct so that we can have fresh member function names. | ||
|  | type ApiServer struct { | ||
|  | 	s *Server | ||
|  | } | ||
|  | 
 | ||
|  | type ApiError struct { | ||
|  | 	Message string | ||
|  | } | ||
|  | 
 | ||
|  | // GetUsers godoc | ||
|  | // @Summary Retrieves all users | ||
|  | // @Produce json | ||
|  | // @Success 200 {object} []users.User | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Router /users [get] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) GetUsers(c *gin.Context) { | ||
|  | 	allUsers := s.s.users.GetUsersUnscoped() | ||
|  | 	for i := range allUsers { | ||
|  | 		allUsers[i].Password = "" // do not publish password... | ||
|  | 	} | ||
|  | 
 | ||
|  | 	c.JSON(http.StatusOK, allUsers) | ||
|  | } | ||
|  | 
 | ||
|  | // GetUser godoc | ||
|  | // @Summary Retrieves user based on given Email | ||
|  | // @Produce json | ||
|  | // @Param email path string true "User Email" | ||
|  | // @Success 200 {object} users.User | ||
|  | // @Failure 400 {object} ApiError | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Router /user/{email} [get] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) GetUser(c *gin.Context) { | ||
|  | 	email := strings.ToLower(strings.TrimSpace(c.Param("email"))) | ||
|  | 
 | ||
|  | 	if email == "" { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must be specified"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	user := s.s.users.GetUserUnscoped(c.Param("email")) | ||
|  | 	if user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user not found"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	user.Password = "" // do not send password... | ||
|  | 	c.JSON(http.StatusOK, user) | ||
|  | } | ||
|  | 
 | ||
|  | // PostUser godoc | ||
|  | // @Summary Creates a new user based on the given user model | ||
|  | // @Produce json | ||
|  | // @Success 200 {object} users.User | ||
|  | // @Failure 400 {object} ApiError | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Failure 500 {object} ApiError | ||
|  | // @Router /users [post] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) PostUser(c *gin.Context) { | ||
|  | 	newUser := users.User{} | ||
|  | 	if err := c.BindJSON(&newUser); err != nil { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if user := s.s.users.GetUserUnscoped(newUser.Email); user != nil { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "user already exists"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err := s.s.CreateUser(newUser, s.s.wg.Cfg.GetDefaultDeviceName()); err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	user := s.s.users.GetUserUnscoped(newUser.Email) | ||
|  | 	if user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user not found"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	user.Password = "" // do not send password... | ||
|  | 	c.JSON(http.StatusOK, user) | ||
|  | } | ||
|  | 
 | ||
|  | // PutUser godoc | ||
|  | // @Summary Updates a user based on the given user model | ||
|  | // @Produce json | ||
|  | // @Param email path string true "User Email" | ||
|  | // @Success 200 {object} users.User | ||
|  | // @Failure 400 {object} ApiError | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Failure 500 {object} ApiError | ||
|  | // @Router /user/{email} [put] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) PutUser(c *gin.Context) { | ||
|  | 	email := strings.ToLower(strings.TrimSpace(c.Param("email"))) | ||
|  | 	if email == "" { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must be specified"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateUser := users.User{} | ||
|  | 	if err := c.BindJSON(&updateUser); err != nil { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Changing email address is not allowed | ||
|  | 	if email != updateUser.Email { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must match the model email address"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if user := s.s.users.GetUserUnscoped(email); user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user does not exist"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err := s.s.UpdateUser(updateUser); err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	user := s.s.users.GetUserUnscoped(email) | ||
|  | 	if user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user not found"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	user.Password = "" // do not send password... | ||
|  | 	c.JSON(http.StatusOK, user) | ||
|  | } | ||
|  | 
 | ||
|  | // PatchUser godoc | ||
|  | // @Summary Updates a user based on the given partial user model | ||
|  | // @Produce json | ||
|  | // @Param email path string true "User Email" | ||
|  | // @Success 200 {object} users.User | ||
|  | // @Failure 400 {object} ApiError | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Failure 500 {object} ApiError | ||
|  | // @Router /user/{email} [patch] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) PatchUser(c *gin.Context) { | ||
|  | 	email := strings.ToLower(strings.TrimSpace(c.Param("email"))) | ||
|  | 	if email == "" { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must be specified"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	patch, err := c.GetRawData() | ||
|  | 	if err != nil { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	user := s.s.users.GetUserUnscoped(email) | ||
|  | 	if user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user does not exist"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	userData, err := json.Marshal(user) | ||
|  | 	if err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	mergedUserData, err := jsonpatch.MergePatch(userData, patch) | ||
|  | 	var mergedUser users.User | ||
|  | 	err = json.Unmarshal(mergedUserData, &mergedUser) | ||
|  | 	if err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// CHanging email address is not allowed | ||
|  | 	if email != mergedUser.Email { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must match the model email address"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err := s.s.UpdateUser(mergedUser); err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	user = s.s.users.GetUserUnscoped(email) | ||
|  | 	if user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user not found"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	user.Password = "" // do not send password... | ||
|  | 	c.JSON(http.StatusOK, user) | ||
|  | } | ||
|  | 
 | ||
|  | // DeleteUser godoc | ||
|  | // @Summary Deletes the specified user | ||
|  | // @Produce json | ||
|  | // @Param email path string true "User Email" | ||
|  | // @Success 204 "No content" | ||
|  | // @Failure 400 {object} ApiError | ||
|  | // @Failure 401 {object} ApiError | ||
|  | // @Failure 403 {object} ApiError | ||
|  | // @Failure 404 {object} ApiError | ||
|  | // @Failure 500 {object} ApiError | ||
|  | // @Router /user/{email} [delete] | ||
|  | // @Security ApiBasicAuth | ||
|  | func (s *ApiServer) DeleteUser(c *gin.Context) { | ||
|  | 	email := strings.ToLower(strings.TrimSpace(c.Param("email"))) | ||
|  | 	if email == "" { | ||
|  | 		c.JSON(http.StatusBadRequest, ApiError{Message: "email parameter must be specified"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var user *users.User | ||
|  | 	if user = s.s.users.GetUserUnscoped(email); user == nil { | ||
|  | 		c.JSON(http.StatusNotFound, ApiError{Message: "user does not exist"}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err := s.s.DeleteUser(*user); err != nil { | ||
|  | 		c.JSON(http.StatusInternalServerError, ApiError{Message: err.Error()}) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	c.Status(http.StatusNoContent) | ||
|  | } |