Skip to content

Commit

Permalink
(BIDS-3093) add some endpoints to public dashboard api (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuccaBitfly authored Jun 6, 2024
1 parent 2d99529 commit 6ff3631
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 46 deletions.
2 changes: 1 addition & 1 deletion backend/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func main() {
defer dataAccessor.Close()

router := api.NewApiRouter(dataAccessor, cfg)
router.Use(api.GetCorsMiddleware(cfg.CorsAllowedHosts), api.GetAuthMiddleware(cfg.ApiKeySecret))
router.Use(api.GetCorsMiddleware(cfg.CorsAllowedHosts))

srv := &http.Server{
Handler: router,
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/api/data_access/data_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type DataAccessor interface {
GetProductSummary() (*t.ProductSummary, error)
// TODO: move to user repository
GetUser(email string) (*t.User, error)
GetUserIdByApiKey(apiKey string) (uint64, error)

GetValidatorsFromSlices(indices []uint64, publicKeys []string) ([]t.VDBValidator, error)

Expand Down
6 changes: 6 additions & 0 deletions backend/pkg/api/data_access/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ func (d *DummyService) GetUser(email string) (*t.User, error) {
return &r, err
}

func (d *DummyService) GetUserIdByApiKey(apiKey string) (uint64, error) {
r := uint64(0)
err := commonFakeData(&r)
return r, err
}

func (d *DummyService) GetProductSummary() (*t.ProductSummary, error) {
r := t.ProductSummary{}
err := commonFakeData(&r)
Expand Down
5 changes: 5 additions & 0 deletions backend/pkg/api/data_access/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func (d *DataAccessService) GetUser(email string) (*t.User, error) {
return result, err
}

func (d *DataAccessService) GetUserIdByApiKey(apiKey string) (uint64, error) {
// TODO @recy21
return d.dummy.GetUserIdByApiKey(apiKey)
}

func (d *DataAccessService) GetUserInfo(userId uint64) (*t.UserInfo, error) {
// TODO @patrick improve and unmock
userInfo := &t.UserInfo{
Expand Down
85 changes: 55 additions & 30 deletions backend/pkg/api/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (
userGroupKey = "user_group"
)

func (h *HandlerService) getUser(r *http.Request) (types.User, error) {
func (h *HandlerService) getUserBySession(r *http.Request) (types.User, error) {
authenticated := h.scs.GetBool(r.Context(), authenticatedKey)
if !authenticated {
return types.User{}, newUnauthorizedErr("not authenticated")
Expand All @@ -37,6 +37,26 @@ func (h *HandlerService) getUser(r *http.Request) (types.User, error) {
}, nil
}

func (h *HandlerService) GetUserIdBySession(r *http.Request) (uint64, error) {
user, err := h.getUserBySession(r)
if err != nil {
return 0, err
}
return user.Id, nil
}

func (h *HandlerService) GetUserIdByApiKey(r *http.Request) (uint64, error) {
apiKey := r.URL.Query().Get("api_key")
if apiKey == "" {
return 0, newUnauthorizedErr("missing api key")
}
userId, err := h.dai.GetUserIdByApiKey(apiKey)
if err != nil {
return userId, newUnauthorizedErr("invalid api key")
}
return userId, nil
}

// Handlers

func (h *HandlerService) InternalPostOauthAuthorize(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -113,34 +133,39 @@ func (h *HandlerService) InternalPostLogout(w http.ResponseWriter, r *http.Reque

// Middlewares

// checks if user has access to dashboard
func (h *HandlerService) VDBAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
dashboardId, err := strconv.ParseUint(mux.Vars(r)["dashboard_id"], 10, 64)
if err != nil {
// if primary id is not used, no need to check access
next.ServeHTTP(w, r)
return
}
// primary id is used -> user needs to have access to dashboard

user, err := h.getUser(r)
if err != nil {
handleErr(w, err)
return
}
dashboard, err := h.dai.GetValidatorDashboardInfo(types.VDBIdPrimary(dashboardId))
if err != nil {
handleErr(w, err)
return
}
// returns a middleware that checks if user has access to dashboard when a primary id is used
// expects a userIdFunc to return user id, probably GetUserIdBySession or GetUserIdByApiKey
func (h *HandlerService) GetVDBAuthMiddleware(userIdFunc func(r *http.Request) (uint64, error)) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
dashboardId, err := strconv.ParseUint(mux.Vars(r)["dashboard_id"], 10, 64)
if err != nil {
// if primary id is not used, no need to check access
next.ServeHTTP(w, r)
return
}
// primary id is used -> user needs to have access to dashboard

userId, err := userIdFunc(r)
if err != nil {
handleErr(w, err)
return
}
dashboard, err := h.dai.GetValidatorDashboardInfo(types.VDBIdPrimary(dashboardId))
if err != nil {
handleErr(w, err)
return
}

if dashboard.UserId != userId {
// user does not have access to dashboard
// the proper error would be 403 Forbidden, but we don't want to leak information so we return 404 Not Found
handleErr(w, newNotFoundErr("dashboard with id %v not found", dashboardId))
return
}

if dashboard.UserId != user.Id {
// user does not have access to dashboard, return 404 to avoid leaking information
handleErr(w, newNotFoundErr("dashboard with id %v not found", dashboardId))
return
}
next.ServeHTTP(w, r)
})
next.ServeHTTP(w, r)
})
}
}
1 change: 1 addition & 0 deletions backend/pkg/api/handlers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ func newBadRequestErr(format string, args ...interface{}) error {
return errWithMsg(errBadRequest, format, args...)
}

//nolint:unparam
func newUnauthorizedErr(format string, args ...interface{}) error {
return errWithMsg(errUnauthorized, format, args...)
}
Expand Down
9 changes: 5 additions & 4 deletions backend/pkg/api/handlers/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ func (h *HandlerService) InternalDeleteAdConfiguration(w http.ResponseWriter, r
// User

func (h *HandlerService) InternalGetUserInfo(w http.ResponseWriter, r *http.Request) {
user, err := h.getUser(r)
// TODO patrick
user, err := h.getUserBySession(r)
if err != nil {
handleErr(w, err)
return
Expand All @@ -97,12 +98,12 @@ func (h *HandlerService) InternalGetUserInfo(w http.ResponseWriter, r *http.Requ
// Dashboards

func (h *HandlerService) InternalGetUserDashboards(w http.ResponseWriter, r *http.Request) {
user, err := h.getUser(r)
userId, err := h.GetUserIdBySession(r)
if err != nil {
handleErr(w, err)
return
}
data, err := h.dai.GetUserDashboards(user.Id)
data, err := h.dai.GetUserDashboards(userId)
if err != nil {
handleErr(w, err)
return
Expand Down Expand Up @@ -177,7 +178,7 @@ func (h *HandlerService) InternalPutAccountDashboardTransactionsSettings(w http.

func (h *HandlerService) InternalPostValidatorDashboards(w http.ResponseWriter, r *http.Request) {
var v validationError
user, err := h.getUser(r)
user, err := h.getUserBySession(r)
if err != nil {
handleErr(w, err)
return
Expand Down
94 changes: 90 additions & 4 deletions backend/pkg/api/handlers/public.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package handlers

import "net/http"
import (
"errors"
"net/http"

"github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gorilla/mux"
)

// All handler function names must include the HTTP method and the path they handle
// Public handlers may only be authenticated by an API key
Expand All @@ -19,7 +25,20 @@ func (h *HandlerService) PublicPostOauthToken(w http.ResponseWriter, r *http.Req
}

func (h *HandlerService) PublicGetUserDashboards(w http.ResponseWriter, r *http.Request) {
returnOk(w, nil)
userId, err := h.GetUserIdByApiKey(r)
if err != nil {
handleErr(w, err)
return
}
data, err := h.dai.GetUserDashboards(userId)
if err != nil {
handleErr(w, err)
return
}
response := types.ApiDataResponse[types.UserDashboardsData]{
Data: *data,
}
returnOk(w, response)
}

func (h *HandlerService) PublicPostAccountDashboards(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -103,15 +122,82 @@ func (h *HandlerService) PublicDeleteValidatorDashboardGroups(w http.ResponseWri
}

func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
returnCreated(w, nil)
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
req := struct {
Validators []string `json:"validators"`
GroupId string `json:"group_id,omitempty"`
}{}
if err := v.checkBody(&req, r); err != nil {
handleErr(w, err)
return
}
indices, pubkeys := v.checkValidatorArray(req.Validators, forbidEmpty)
groupId := v.checkGroupId(req.GroupId, allowEmpty)
if v.hasErrors() {
handleErr(w, v)
return
}
// empty group id becomes default group
if groupId == types.AllGroups {
groupId = types.DefaultGroupId
}
groupExists, err := h.dai.GetValidatorDashboardGroupExists(dashboardId, uint64(groupId))
if err != nil {
handleErr(w, err)
return
}
if !groupExists {
returnNotFound(w, errors.New("group not found"))
return
}
validators, err := h.dai.GetValidatorsFromSlices(indices, pubkeys)
if err != nil {
handleErr(w, err)
return
}
// TODO check validator limit reached
data, err := h.dai.AddValidatorDashboardValidators(dashboardId, groupId, validators)
if err != nil {
handleErr(w, err)
return
}

response := types.ApiResponse{
Data: data,
}

returnCreated(w, response)
}

func (h *HandlerService) PublicGetValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
returnOk(w, nil)
}

func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
returnOk(w, nil)
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
var indices []uint64
var publicKeys []string
if validatorsParam := r.URL.Query().Get("validators"); validatorsParam != "" {
indices, publicKeys = v.checkValidatorList(validatorsParam, allowEmpty)
if v.hasErrors() {
handleErr(w, v)
return
}
}
validators, err := h.dai.GetValidatorsFromSlices(indices, publicKeys)
if err != nil {
handleErr(w, err)
return
}
err = h.dai.RemoveValidatorDashboardValidators(dashboardId, validators)
if err != nil {
handleErr(w, err)
return
}

returnNoContent(w)
}

func (h *HandlerService) PublicPostValidatorDashboardPublicIds(w http.ResponseWriter, r *http.Request) {
Expand Down
14 changes: 7 additions & 7 deletions backend/pkg/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func NewApiRouter(dataAccessor dataaccess.DataAccessor, cfg *types.Config) *mux.
router.HandleFunc("/test/stripe", TestStripe).Methods(http.MethodGet)
apiRouter.HandleFunc("/test/stripe", TestStripe).Methods(http.MethodGet)

addRoutes(handlerService, publicRouter, internalRouter, debug)
addRoutes(handlerService, publicRouter, internalRouter, cfg)

return router
}
Expand Down Expand Up @@ -211,8 +211,8 @@ func GetCorsMiddleware(allowedHosts []string) func(http.Handler) http.Handler {
)
}

func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Router, debug bool) {
addValidatorDashboardRoutes(hs, publicRouter, internalRouter, debug)
func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Router, cfg *types.Config) {
addValidatorDashboardRoutes(hs, publicRouter, internalRouter, cfg)
endpoints := []endpoint{
{http.MethodGet, "/healthz", hs.PublicGetHealthz, nil},
{http.MethodGet, "/healthz-loadbalancer", hs.PublicGetHealthzLoadbalancer, nil},
Expand Down Expand Up @@ -342,17 +342,17 @@ func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Ro
addEndpointsToRouters(endpoints, publicRouter, internalRouter)
}

func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Router, debug bool) {
func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Router, cfg *types.Config) {
vdbPath := "/validator-dashboards"
publicRouter.HandleFunc(vdbPath, hs.PublicPostValidatorDashboards).Methods(http.MethodPost, http.MethodOptions)
internalRouter.HandleFunc(vdbPath, hs.InternalPostValidatorDashboards).Methods(http.MethodPost, http.MethodOptions)

publicDashboardRouter := publicRouter.PathPrefix(vdbPath).Subrouter()
internalDashboardRouter := internalRouter.PathPrefix(vdbPath).Subrouter()
// add middleware to check if user has access to dashboard
if !debug {
publicDashboardRouter.Use(hs.VDBAuthMiddleware)
internalDashboardRouter.Use(hs.VDBAuthMiddleware)
if !cfg.Frontend.Debug {
publicDashboardRouter.Use(hs.GetVDBAuthMiddleware(hs.GetUserIdByApiKey))
internalDashboardRouter.Use(hs.GetVDBAuthMiddleware(hs.GetUserIdBySession), GetAuthMiddleware(cfg.ApiKeySecret))
}

endpoints := []endpoint{
Expand Down

0 comments on commit 6ff3631

Please sign in to comment.