2026-02-10 11:32:49 -08:00
|
|
|
package rest
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
|
fix(auth): typed context keys and real sentinel errors
backend/api/middleware/context.go (new):
- Introduces an unexported ctxKey type and three constants
(ctxKeyUserAddress, ctxKeyUserTrack, ctxKeyAuthenticated) that
replace the bare string keys 'user_address', 'user_track', and
'authenticated'. Bare strings trigger go vet's SA1029 and collide
with keys from any other package that happens to share the name.
- Helpers: ContextWithAuth, UserAddress, UserTrack, IsAuthenticated.
- Sentinel: ErrMissingAuthorization replaces the misuse of
http.ErrMissingFile as an auth-missing signal. (http.ErrMissingFile
belongs to multipart form parsing and was semantically wrong.)
backend/api/middleware/auth.go:
- RequireAuth, OptionalAuth, RequireTrack now all read/write via the
helpers; no more string literals for context keys in this file.
- extractAuth returns ErrMissingAuthorization instead of
http.ErrMissingFile.
- Dropped now-unused 'context' import.
backend/api/track4/operator_scripts.go, backend/api/track4/endpoints.go,
backend/api/rest/features.go:
- Read user address / track via middleware.UserAddress() and
middleware.UserTrack() instead of a raw context lookup with a bare
string key.
- Import 'github.com/explorer/backend/api/middleware'.
backend/api/track4/operator_scripts_test.go:
- Four test fixtures updated to seed the request context through
middleware.ContextWithAuth (track 4, authenticated) instead of
context.WithValue with a bare 'user_address' string. This is the
load-bearing change that proves typed keys are required: a bare
string key no longer wakes up the middleware helpers.
backend/api/middleware/context_test.go (new):
- Round-trip test for ContextWithAuth + UserAddress + UserTrack +
IsAuthenticated.
- Defaults: UserTrack=1, UserAddress="", IsAuthenticated=false on a
bare context.
- TestContextKeyIsolation: an outside caller that inserts
'user_address' as a bare string key must NOT be visible to
UserAddress; proves the type discipline.
- ErrMissingAuthorization sentinel smoke test.
Verification:
- go build ./... clean.
- go vet ./... clean (removes SA1029 on the old bare keys).
- go test ./api/middleware/... ./api/track4/... ./api/rest/... PASS.
Advances completion criterion 3 (Auth correctness).
2026-04-18 19:05:24 +00:00
|
|
|
"github.com/explorer/backend/api/middleware"
|
2026-02-10 11:32:49 -08:00
|
|
|
"github.com/explorer/backend/featureflags"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// handleFeatures handles GET /api/v1/features
|
|
|
|
|
// Returns available features for the current user based on their track level
|
|
|
|
|
func (s *Server) handleFeatures(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.Method != http.MethodGet {
|
|
|
|
|
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract user track from context (set by auth middleware)
|
fix(auth): typed context keys and real sentinel errors
backend/api/middleware/context.go (new):
- Introduces an unexported ctxKey type and three constants
(ctxKeyUserAddress, ctxKeyUserTrack, ctxKeyAuthenticated) that
replace the bare string keys 'user_address', 'user_track', and
'authenticated'. Bare strings trigger go vet's SA1029 and collide
with keys from any other package that happens to share the name.
- Helpers: ContextWithAuth, UserAddress, UserTrack, IsAuthenticated.
- Sentinel: ErrMissingAuthorization replaces the misuse of
http.ErrMissingFile as an auth-missing signal. (http.ErrMissingFile
belongs to multipart form parsing and was semantically wrong.)
backend/api/middleware/auth.go:
- RequireAuth, OptionalAuth, RequireTrack now all read/write via the
helpers; no more string literals for context keys in this file.
- extractAuth returns ErrMissingAuthorization instead of
http.ErrMissingFile.
- Dropped now-unused 'context' import.
backend/api/track4/operator_scripts.go, backend/api/track4/endpoints.go,
backend/api/rest/features.go:
- Read user address / track via middleware.UserAddress() and
middleware.UserTrack() instead of a raw context lookup with a bare
string key.
- Import 'github.com/explorer/backend/api/middleware'.
backend/api/track4/operator_scripts_test.go:
- Four test fixtures updated to seed the request context through
middleware.ContextWithAuth (track 4, authenticated) instead of
context.WithValue with a bare 'user_address' string. This is the
load-bearing change that proves typed keys are required: a bare
string key no longer wakes up the middleware helpers.
backend/api/middleware/context_test.go (new):
- Round-trip test for ContextWithAuth + UserAddress + UserTrack +
IsAuthenticated.
- Defaults: UserTrack=1, UserAddress="", IsAuthenticated=false on a
bare context.
- TestContextKeyIsolation: an outside caller that inserts
'user_address' as a bare string key must NOT be visible to
UserAddress; proves the type discipline.
- ErrMissingAuthorization sentinel smoke test.
Verification:
- go build ./... clean.
- go vet ./... clean (removes SA1029 on the old bare keys).
- go test ./api/middleware/... ./api/track4/... ./api/rest/... PASS.
Advances completion criterion 3 (Auth correctness).
2026-04-18 19:05:24 +00:00
|
|
|
// Default to Track 1 (public) if not authenticated (handled by helper).
|
|
|
|
|
userTrack := middleware.UserTrack(r.Context())
|
2026-02-10 11:32:49 -08:00
|
|
|
|
|
|
|
|
// Get enabled features for this track
|
|
|
|
|
enabledFeatures := featureflags.GetEnabledFeatures(userTrack)
|
|
|
|
|
|
|
|
|
|
// Get permissions based on track
|
|
|
|
|
permissions := getPermissionsForTrack(userTrack)
|
|
|
|
|
|
|
|
|
|
response := map[string]interface{}{
|
|
|
|
|
"track": userTrack,
|
|
|
|
|
"features": enabledFeatures,
|
|
|
|
|
"permissions": permissions,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
json.NewEncoder(w).Encode(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getPermissionsForTrack returns permissions for a given track level
|
|
|
|
|
func getPermissionsForTrack(track int) []string {
|
|
|
|
|
permissions := []string{
|
|
|
|
|
"explorer.read.blocks",
|
|
|
|
|
"explorer.read.transactions",
|
|
|
|
|
"explorer.read.address.basic",
|
|
|
|
|
"explorer.read.bridge.status",
|
|
|
|
|
"weth.wrap",
|
|
|
|
|
"weth.unwrap",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if track >= 2 {
|
|
|
|
|
permissions = append(permissions,
|
|
|
|
|
"explorer.read.address.full",
|
|
|
|
|
"explorer.read.tokens",
|
|
|
|
|
"explorer.read.tx_history",
|
|
|
|
|
"explorer.read.internal_txs",
|
|
|
|
|
"explorer.search.enhanced",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if track >= 3 {
|
|
|
|
|
permissions = append(permissions,
|
|
|
|
|
"analytics.read.flows",
|
|
|
|
|
"analytics.read.bridge",
|
|
|
|
|
"analytics.read.token_distribution",
|
|
|
|
|
"analytics.read.address_risk",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if track >= 4 {
|
|
|
|
|
permissions = append(permissions,
|
|
|
|
|
"operator.read.bridge_events",
|
|
|
|
|
"operator.read.validators",
|
|
|
|
|
"operator.read.contracts",
|
|
|
|
|
"operator.read.protocol_state",
|
|
|
|
|
"operator.write.bridge_control",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return permissions
|
|
|
|
|
}
|