Skip to content

Commit

Permalink
feat: added authentication in API and search api
Browse files Browse the repository at this point in the history
	Get license with specific search term:
		field: represents the name of field to be searched
		search_term: represents the term to be searched
		search: represent the algorithm to search with

	authenticate the API:
		added the struct for user
		created a user table in the database
		added basic user endpoints:
			get user using id
			get all user
			create user
		added basic authentication to all the API endpoints i.e, group of endpoints
  • Loading branch information
k-avy committed Jul 14, 2023
1 parent dbc69bc commit cb81526
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 4 deletions.
17 changes: 13 additions & 4 deletions cmd/laas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"log"

"github.com/fossology/LicenseDb/pkg/api"
"github.com/fossology/LicenseDb/pkg/authenticate"
"github.com/fossology/LicenseDb/pkg/models"
"github.com/fossology/LicenseDb/pkg/utils"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -50,6 +51,9 @@ func main() {
log.Fatalf("Failed to automigrate database: %v", err)
}

if err := database.AutoMigrate(&models.User{}); err != nil {
log.Fatalf("Failed to automigrate database: %v", err)
}
if *populatedb {
var licenses []models.LicenseJson
// read the file of data
Expand All @@ -68,9 +72,14 @@ func main() {

r := gin.Default()
r.NoRoute(api.HandleInvalidUrl)
r.GET("/api/licenses", api.GetAllLicense)
r.GET("/api/license/:shortname", api.GetLicense)
r.POST("/api/license", api.CreateLicense)
r.PATCH("/api/license/:shortname", api.UpdateLicense)
authorized := r.Group("/")
authorized.Use(authenticate.AuthenticationMiddleware())
authorized.GET("/api/license/:shortname", api.GetLicense)
authorized.POST("/api/license", api.CreateLicense)
authorized.PATCH("/api/license/update/:shortname", api.UpdateLicense)
authorized.GET("/api/licenses", api.SearchInLicense)
r.POST("/api/user", authenticate.CreateUser)
authorized.GET("/api/users", authenticate.GetAllUser)
authorized.GET("/api/user/:id", authenticate.GetUser)
r.Run()
}
50 changes: 50 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package api

import (
"errors"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -188,3 +189,52 @@ func UpdateLicense(c *gin.Context) {
c.JSON(http.StatusOK, res)

}

func SearchInLicense(c *gin.Context) {
field := c.Query("field")
search_term := c.Query("search_term")
search := c.Query("search")
if field == "" && search_term == "" {
GetAllLicense(c)
return
}
var query *gorm.DB
var license []models.License
if search == "fuzzy" {
query = DB.Where(fmt.Sprintf("%s ILIKE ?", field), fmt.Sprintf("%%%s%%", search_term)).Find(&license)
} else if search == "" || search == "full_text_search" {
query = DB.Where(field+" @@ plainto_tsquery(?)", search_term).Find(&license)
} else {
err := errors.New("")
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("incorrect query to search in the database"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusBadRequest, er)
return
}

if err := query.Error; err != nil {
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("incorrect query to search in the database"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusBadRequest, er)
return
}
res := models.LicenseResponse{
Data: license,
Status: http.StatusOK,
Meta: models.Meta{
ResourceCount: len(license),
},
}
c.JSON(http.StatusOK, res)

}
181 changes: 181 additions & 0 deletions pkg/authenticate/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-FileCopyrightText: 2023 Kavya Shukla <[email protected]>
// SPDX-License-Identifier: GPL-2.0-only

package authenticate

import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/fossology/LicenseDb/pkg/api"
"github.com/fossology/LicenseDb/pkg/models"
"github.com/gin-gonic/gin"
)

func CreateUser(c *gin.Context) {
var user models.User
err := c.ShouldBindJSON(&user)
if err != nil {
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("invalid request"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusBadRequest, er)
return
}

result := api.DB.Create(&user)
if result.Error != nil {
er := models.LicenseError{
Status: http.StatusInternalServerError,
Message: fmt.Sprintf("internal Server Error"),
Error: result.Error.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusInternalServerError, er)
return
}
res := models.UserResponse{
Data: []models.User{user},
Status: http.StatusOK,
Meta: models.Meta{
ResourceCount: 1,
},
}

c.JSON(http.StatusOK, res)
}

func GetAllUser(c *gin.Context) {
var users []models.User
err := api.DB.Find(&users).Error
if err != nil {
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("invalid request"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusBadRequest, er)
}
res := models.UserResponse{
Data: users,
Status: http.StatusOK,
Meta: models.Meta{
ResourceCount: 1,
},
}

c.JSON(http.StatusOK, res)
}

func GetUser(c *gin.Context) {
var user models.User
id := c.Param("id")
err := api.DB.Where("id = ?", id).First(&user).Error
if err != nil {
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("invalid request"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusBadRequest, er)
}
res := models.UserResponse{
Data: []models.User{user},
Status: http.StatusOK,
Meta: models.Meta{
ResourceCount: 1,
},
}

c.JSON(http.StatusOK, res)
}

func AuthenticationMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
err := errors.New("authentication failed")
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("Please check your credentials and try again"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}

c.JSON(http.StatusUnauthorized, er)
c.Abort()
return
}

decodedAuth, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic "))
if err != nil {
er := models.LicenseError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("Please check your credentials and try again"),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}

c.JSON(http.StatusBadRequest, er)
c.Abort()
return
}

auth := strings.SplitN(string(decodedAuth), ":", 2)
if len(auth) != 2 {
c.AbortWithStatus(http.StatusBadRequest)
return
}

username := auth[0]
password := auth[1]

var user models.User
result := api.DB.Where("username = ?", username).First(&user)
if result.Error != nil {
er := models.LicenseError{
Status: http.StatusUnauthorized,
Message: fmt.Sprintf("User name not found"),
Error: result.Error.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}

c.JSON(http.StatusUnauthorized, er)
c.Abort()
return
}

// Check if the password matches
if user.Userpassword != password {
er := models.LicenseError{
Status: http.StatusUnauthorized,
Message: fmt.Sprintf("Incorrect password"),
Error: result.Error.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}

c.JSON(http.StatusUnauthorized, er)
c.Abort()
return
}

c.Next()
}
}
15 changes: 15 additions & 0 deletions pkg/models/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,18 @@ type LicenseInput struct {
Flag string `json:"rf_flag"`
Marydone string `json:"marydone"`
}

// User struct is representation of user information.
type User struct {
Userid string `json:"userid" gorm:"primary_key"`
Username string `json:"username" gorm:"unique"`
Userlevel string `json:"userlevel" gorm:"unique"`
Userpassword string `json:"userpassword" gorm:"unique"`
}

// UserResponse struct is representation of design API response of user.
type UserResponse struct {
Status int `json:"status"`
Data []User `json:"data"`
Meta Meta `json:"meta"`
}

0 comments on commit cb81526

Please sign in to comment.