Skip to content

Commit

Permalink
api: init openapi handlers (#30)
Browse files Browse the repository at this point in the history
* cmd: fix config flag

* api: init openapi handlers
  • Loading branch information
scriptnull authored Nov 6, 2024
1 parent 030980e commit bd45667
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 67 deletions.
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ require (

require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 // indirect
github.com/scriptnull/jsonseal v0.3.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/swaggest/form/v5 v5.1.1 // indirect
github.com/swaggest/jsonschema-go v0.3.72 // indirect
github.com/swaggest/openapi-go v0.2.54 // indirect
github.com/swaggest/refl v1.3.0 // indirect
github.com/swaggest/rest v0.2.68 // indirect
github.com/swaggest/swgui v1.8.2 // indirect
github.com/swaggest/usecase v1.3.1 // indirect
github.com/vearutop/statigz v1.4.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
26 changes: 26 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand All @@ -16,23 +20,45 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 h1:levPcBfnazlA1CyCMC3asL/QLZkq9pa8tQZOH513zQw=
github.com/santhosh-tekuri/jsonschema/v3 v3.1.0/go.mod h1:8kzK2TC0k0YjOForaAHdNEa7ik0fokNa2k30BKJ/W7Y=
github.com/scriptnull/jsonseal v0.3.0 h1:Ku5AQx0EsB+DRitQ5YG1YcyH8kXaQzJTRnKQsGNtThE=
github.com/scriptnull/jsonseal v0.3.0/go.mod h1:Nebbv7awTxOboVQ7CDxdO4ImxTFBNvYpr5GI0/xZ1cM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4=
github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4=
github.com/swaggest/openapi-go v0.2.54 h1:WnFKIHAgR2RIOiYys3qvSuYmsFd2a17MIoC9Tcvog5c=
github.com/swaggest/openapi-go v0.2.54/go.mod h1:2Q7NpuG9NgpGeTaNOo852GSR6cCzSP4IznA9DNdUTQw=
github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
github.com/swaggest/rest v0.2.68 h1:xYF3CgKLgO+aODFxV5H9jZRrO7z3t5xNP8/H38hGojU=
github.com/swaggest/rest v0.2.68/go.mod h1:jf/wNhDFY7TPEsSGooy2ZEimtaNEnvpaU6SPlTaWTO4=
github.com/swaggest/swgui v1.8.2 h1:JGpRCLGLZ7EqTwHsBEOo//kx8CM7Rv3RchgvfNpB+6E=
github.com/swaggest/swgui v1.8.2/go.mod h1:nkzGeyMfq5FstGGNJKr1LORvM4RdsjTmvWvqvyZeDDc=
github.com/swaggest/usecase v1.3.1 h1:JdKV30MTSsDxAXxkldLNcEn8O2uf565khyo6gr5sS+w=
github.com/swaggest/usecase v1.3.1/go.mod h1:cae3lDd5VDmM36OQcOOOdAlEDg40TiQYIp99S9ejWqA=
github.com/vearutop/statigz v1.4.0 h1:RQL0KG3j/uyA/PFpHeZ/L6l2ta920/MxlOAIGEOuwmU=
github.com/vearutop/statigz v1.4.0/go.mod h1:LYTolBLiz9oJISwiVKnOQoIwhO1LWX1A7OECawGS8XE=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 6 additions & 2 deletions internal/restapi/server/middlerware.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import (
"strings"
)

func BearerTokenAuth(next http.Handler, authToken string) http.Handler {
type BearerTokenAuth struct {
AuthToken string
}

func (b *BearerTokenAuth) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" || !strings.HasPrefix(token, "Bearer ") {
Expand All @@ -14,7 +18,7 @@ func BearerTokenAuth(next http.Handler, authToken string) http.Handler {
}

providedToken := strings.TrimPrefix(token, "Bearer ")
if providedToken != authToken {
if providedToken != b.AuthToken {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Expand Down
25 changes: 18 additions & 7 deletions internal/restapi/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import (

"github.com/ashwiniag/goKakashi/internal/restapi/v0/scan"
"github.com/ashwiniag/goKakashi/pkg/config"
"github.com/gorilla/mux"
"github.com/swaggest/openapi-go/openapi31"
"github.com/swaggest/rest/web"
swgui "github.com/swaggest/swgui/v5emb"
"github.com/swaggest/usecase"
)

type Server struct {
Expand All @@ -17,17 +20,25 @@ type Server struct {
Port int
}

func (s *Server) Router() *mux.Router {
r := mux.NewRouter()
func (srv *Server) Service() *web.Service {
s := web.NewService(openapi31.NewReflector())

r.Handle("/api/v0/scan", BearerTokenAuth(&scan.PostHandler{Websites: s.Websites}, s.AuthToken)).Methods("POST")
r.Handle("/api/v0/scan/{scan_id}", BearerTokenAuth(http.HandlerFunc(scan.GetHandler), s.AuthToken)).Methods("GET")
s.OpenAPISchema().SetTitle("GoKakashi API")
s.OpenAPISchema().SetDescription("This is the GoKakashi REST API.")
s.OpenAPISchema().SetVersion("v0.0.1")

return r
bearerAuth := &BearerTokenAuth{AuthToken: srv.AuthToken}
s.Wrap(bearerAuth.Middleware)

s.Post("/api/v0/scan", usecase.NewInteractor(scan.Post))
s.Get("/api/v0/scan/{scan_id}", usecase.NewInteractor(scan.Get))

s.Docs("/docs", swgui.New)
return s
}

func (s *Server) Serve() {
err := http.ListenAndServe(":"+strconv.Itoa(s.Port), s.Router())
err := http.ListenAndServe(":"+strconv.Itoa(s.Port), s.Service())
if err != nil {
log.Println("Error starting up the server", err)
return
Expand Down
41 changes: 16 additions & 25 deletions internal/restapi/v0/scan/get.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
package scan

import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"

"github.com/ashwiniag/goKakashi/internal/imgscan"
"github.com/gorilla/mux"
"github.com/swaggest/usecase/status"
)

type GetRequest struct {
ScanID string `path:"scan_id"`
}

type GetResponse struct {
ScanID string `json:"scanID"`
Status string `json:"status"`
ReportURLs []string `json:"report_url,omitempty"` // Optional field
}

func GetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
scanID := vars["scan_id"]

log.Printf("Get scan status for scanID %s", scanID)
_, status, err := imgscan.GetScanStatus(scanID)
func Get(_ context.Context, req GetRequest, res *GetResponse) error {
_, s, err := imgscan.GetScanStatus(req.ScanID)
if err != nil {
http.Error(w, jsonErrorResponse(fmt.Sprintf("Scan ID %s not found", scanID)), http.StatusNotFound)
return
return status.Wrap(errors.New("scan id not found"), status.InvalidArgument)
}

// Create the response
response := GetResponse{
ScanID: scanID,
Status: status,
}
res.ScanID = req.ScanID
res.Status = s

// If the status is completed, add the report URL
// Todo: to provide correct file path
if status == string(imgscan.StatusCompleted) {
reportFilePath := fmt.Sprintf("/tmp/%s.json", scanID)
if res.Status == string(imgscan.StatusCompleted) {
reportFilePath := fmt.Sprintf("/tmp/%s.json", req.ScanID)
fileData, err := os.ReadFile(reportFilePath)
if err == nil {
var result map[string]interface{}
Expand All @@ -51,16 +47,11 @@ func GetHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Set all URLs in the response
response.ReportURLs = reportURLs
res.ReportURLs = reportURLs
}
}
}
}

w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(response)
if err != nil {
log.Println("Error responsing json", err)
return
}
return nil
}
44 changes: 11 additions & 33 deletions internal/restapi/v0/scan/post.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package scan

import (
"encoding/json"
"context"
_ "encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
_ "strings"
"time"
Expand All @@ -15,6 +13,7 @@ import (
"github.com/ashwiniag/goKakashi/pkg/config"
_ "github.com/ashwiniag/goKakashi/pkg/utils"
"github.com/scriptnull/jsonseal"
"github.com/swaggest/usecase/status"
"golang.org/x/exp/maps"
)

Expand All @@ -25,8 +24,8 @@ type PostRequest struct {
}

type PostResponse struct {
ScanID string `json:"scan_id"`
Status string `json:"status"`
ScanID string `json:"scan_id"`
Status imgscan.ScanStatus `json:"status"`
}

type PostHandler struct {
Expand Down Expand Up @@ -72,41 +71,20 @@ func (req *PostRequest) Validate() error {
return check.Validate()
}

func (ph *PostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var req PostRequest

err := jsonseal.NewDecoder(r.Body).DecodeValidate(&req)
func Post(_ context.Context, req PostRequest, res *PostResponse) error {
err := req.Validate()
if err != nil {
http.Error(w, jsonErrorResponse(jsonseal.JSONFormat(err)), http.StatusBadRequest)
return
return status.Wrap(err, status.InvalidArgument)
}

scanID := generateScanID()
imgscan.UpdateScanStatus(scanID, imgscan.StatusQueued)

log.Printf("Initiating scan for image %s with severity %s", req.Image, req.Severity)
go imgscan.RunScan(scanID, req.Image, req.Severity, req.Publish, ph.Websites)
res.ScanID = generateScanID()
res.Status = imgscan.StatusQueued
imgscan.UpdateScanStatus(res.ScanID, res.Status)

response := PostResponse{
ScanID: scanID,
Status: string(imgscan.StatusQueued),
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(response)
if err != nil {
log.Println("Error responding json", err)
return
}
return nil
}

// Generate a unique scan ID based on the current timestamp
func generateScanID() string {
return fmt.Sprintf("scan-%d", time.Now().UnixNano())
}

// jsonErrorResponse creates a unified JSON error response
func jsonErrorResponse(message string) string {
response := map[string]string{"error": message}
jsonResponse, _ := json.Marshal(response) // Ignore error for simplicity
return string(jsonResponse)
}

0 comments on commit bd45667

Please sign in to comment.