Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ go-fdo-server
go-fdo-server-*.tar.*
rpmbuild
test/workdir

# Generated OpenAPI code
generated/
47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,53 @@ ARCH := $(shell uname -m)
# Default target
all: build test

#
# OpenAPI Code Generation
#
OPENAPI_SPEC := api/openapi/owner-server.yaml
GENERATED_DIR := generated

.PHONY: validate-openapi
validate-openapi:
@echo "Validating OpenAPI specification..."
@command -v openapi-generator >/dev/null 2>&1 || { \
echo "Error: openapi-generator CLI not found. Please install it first:"; \
echo " npm install @openapitools/openapi-generator-cli -g"; \
echo " or"; \
echo " brew install openapi-generator"; \
exit 1; \
}
openapi-generator validate -i $(OPENAPI_SPEC)

.PHONY: generate-api
generate-api: validate-openapi
@echo "Generating Go server and client code from OpenAPI specification..."
@mkdir -p $(GENERATED_DIR)/server $(GENERATED_DIR)/client
openapi-generator generate \
-i $(OPENAPI_SPEC) \
-g go-server \
-o $(GENERATED_DIR)/server \
--additional-properties=packageName=openapi,outputAsLibrary=true,sourceFolder=src/main/go
openapi-generator generate \
-i $(OPENAPI_SPEC) \
-g go \
-o $(GENERATED_DIR)/client \
Comment on lines +33 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a Makefile target to generate the api I would prefer to use the golang native approach to generate code, please see https://github.com/oapi-codegen/oapi-codegen/?tab=readme-ov-file#for-go-124 as an example. We will need a Makefile target anyway but in this case it will run go generate and we will make the build target to depend on it.

This leads me to open the discussion about using the openapi generator tools (java) or the more native oapi-codegen shown in the link above.

After thinking a lot about this I think we should use the native tool as it's very well documented and used by other projects, but as I said this is open to discussion.

--additional-properties=packageName=client

.PHONY: clean-generated
clean-generated:
@echo "Cleaning generated OpenAPI code..."
rm -rf $(GENERATED_DIR)

.PHONY: openapi-docs
openapi-docs:
@echo "Starting OpenAPI documentation server..."
@command -v swagger-ui-serve >/dev/null 2>&1 || { \
echo "Installing swagger-ui-serve..."; \
npm install -g swagger-ui-serve; \
}
swagger-ui-serve $(OPENAPI_SPEC)

# Build the Go project
.PHONY: build
build: tidy fmt vet
Expand Down
6 changes: 3 additions & 3 deletions api/handlers/ownerinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ func OwnerInfoHandler(w http.ResponseWriter, r *http.Request) {
}
}

func getOwnerInfo(w http.ResponseWriter, _ *http.Request) {
func getOwnerInfo(w http.ResponseWriter, r *http.Request) {
slog.Debug("Fetching ownerInfo")
ownerInfoJSON, err := db.FetchOwnerInfoJSON()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
slog.Error("No ownerInfo found")
http.Error(w, "No ownerInfo found", http.StatusNotFound)
WriteErrorResponse(w, r, http.StatusNotFound, "No ownerInfo found", "Owner redirect information has not been configured", "No ownerInfo found")
} else {
slog.Error("Error fetching ownerInfo", "error", err)
http.Error(w, "Error fetching ownerInfo", http.StatusInternalServerError)
WriteErrorResponse(w, r, http.StatusInternalServerError, "Error fetching ownerInfo", err.Error(), "Error fetching ownerInfo")
}
return
}
Expand Down
108 changes: 108 additions & 0 deletions api/handlers/responses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache 2.0

package handlers

import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/http"

"github.com/fido-device-onboard/go-fdo-server/internal/utils"
)

// VoucherResponse represents a voucher in JSON format
type VoucherResponse struct {
Voucher string `json:"voucher"`
Encoding string `json:"encoding"`
GUID string `json:"guid"`
}

// VoucherInsertResponse represents the result of voucher insertion
type VoucherInsertResponse struct {
Processed int `json:"processed"`
Inserted int `json:"inserted"`
Errors []string `json:"errors,omitempty"`
}

// ErrorResponse represents a standard error response
type ErrorResponse struct {
Error string `json:"error"`
Details string `json:"details,omitempty"`
}

// WriteJSONVoucher writes a voucher response in JSON format
func WriteJSONVoucher(w http.ResponseWriter, voucherData []byte, encoding string, guid []byte) error {
response := VoucherResponse{
Voucher: base64.StdEncoding.EncodeToString(voucherData),
Encoding: encoding,
GUID: hex.EncodeToString(guid),
}

w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(response)
}

// WriteJSONError writes a standard error response in JSON format
func WriteJSONError(w http.ResponseWriter, statusCode int, errorMsg, details string) {
response := ErrorResponse{
Error: errorMsg,
Details: details,
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
}

// WriteJSONVoucherInsertResponse writes a voucher insert response in JSON format
func WriteJSONVoucherInsertResponse(w http.ResponseWriter, response VoucherInsertResponse) error {
w.Header().Set("Content-Type", "application/json")
if len(response.Errors) > 0 && response.Inserted == 0 {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusOK)
}
return json.NewEncoder(w).Encode(response)
}

// ShouldReturnJSON checks if the client prefers JSON response
// This allows for backward compatibility with existing PEM clients
func ShouldReturnJSON(r *http.Request) bool {
accept := r.Header.Get("Accept")

// If client specifically requests PEM, honor it for backward compatibility
if accept == "application/x-pem-file" {
return false
}

// Default to JSON for everything else (including no preference)
return true
}

// WriteErrorResponse writes an error response in JSON or text format based on Accept header
func WriteErrorResponse(w http.ResponseWriter, r *http.Request, statusCode int, jsonMsg, jsonDetails, textMsg string) {
if ShouldReturnJSON(r) {
WriteJSONError(w, statusCode, jsonMsg, jsonDetails)
} else {
http.Error(w, textMsg, statusCode)
}
}

// ValidateAndDecodeGUID validates a GUID string and returns the decoded bytes
// Returns the decoded GUID bytes or an error response written to the writer
func ValidateAndDecodeGUID(w http.ResponseWriter, r *http.Request, guidHex string) ([]byte, bool) {
if !utils.IsValidGUID(guidHex) {
WriteErrorResponse(w, r, http.StatusBadRequest, "Invalid GUID", "GUID must be 32 hexadecimal characters", "Invalid GUID")
return nil, false
}

guid, err := hex.DecodeString(guidHex)
if err != nil {
WriteErrorResponse(w, r, http.StatusBadRequest, "Invalid GUID format", err.Error(), "Invalid GUID format")
return nil, false
}

return guid, true
}
Loading
Loading