Skip to content

Commit

Permalink
feat: add support for authorization via GLOBALPING_TOKEN (jsdelivr#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
radulucut authored Jun 28, 2024
1 parent a5ad37b commit 03a8dfa
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 29 deletions.
11 changes: 7 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package cmd

import (
"github.com/spf13/pflag"
"golang.org/x/term"
"os"
"os/signal"
"syscall"

"github.com/spf13/pflag"
"golang.org/x/term"

"github.com/jsdelivr/globalping-cli/globalping"
"github.com/jsdelivr/globalping-cli/globalping/probe"
"github.com/jsdelivr/globalping-cli/utils"
Expand All @@ -30,13 +31,15 @@ type Root struct {
func Execute() {
utime := utils.NewTime()
printer := view.NewPrinter(os.Stdin, os.Stdout, os.Stderr)
config := utils.NewConfig()
config.Load()
ctx := &view.Context{
APIMinInterval: globalping.API_MIN_INTERVAL,
APIMinInterval: config.GlobalpingAPIInterval,
History: view.NewHistoryBuffer(10),
From: "world",
Limit: 1,
}
globalpingClient := globalping.NewClient(globalping.API_URL)
globalpingClient := globalping.NewClient(config)
globalpingProbe := probe.NewProbe()
viewer := view.NewViewer(ctx, printer, utime, globalpingClient)
root := NewRoot(printer, ctx, viewer, utime, globalpingClient, globalpingProbe)
Expand Down
8 changes: 5 additions & 3 deletions globalping/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package globalping
import (
"net/http"
"time"

"github.com/jsdelivr/globalping-cli/utils"
)

type Client interface {
Expand All @@ -13,18 +15,18 @@ type Client interface {

type client struct {
http *http.Client
apiUrl string // The api url endpoint
config *utils.Config

etags map[string]string // caches Etags by measurement id
measurements map[string][]byte // caches Measurements by ETag
}

func NewClient(url string) Client {
func NewClient(config *utils.Config) Client {
return &client{
http: &http.Client{
Timeout: 30 * time.Second,
},
apiUrl: url,
config: config,
etags: map[string]string{},
measurements: map[string][]byte{},
}
Expand Down
19 changes: 11 additions & 8 deletions globalping/globalping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@ import (
"fmt"
"io"
"net/http"
"time"

"github.com/andybalholm/brotli"
"github.com/jsdelivr/globalping-cli/version"
)

var (
API_URL = "https://api.globalping.io/v1/measurements"
API_MIN_INTERVAL = 500 * time.Millisecond
)

// boolean indicates whether to print CLI help on error
func (c *client) CreateMeasurement(measurement *MeasurementCreate) (*MeasurementCreateResponse, bool, error) {
postData, err := json.Marshal(measurement)
Expand All @@ -26,14 +20,18 @@ func (c *client) CreateMeasurement(measurement *MeasurementCreate) (*Measurement
}

// Create a new request
req, err := http.NewRequest("POST", c.apiUrl, bytes.NewBuffer(postData))
req, err := http.NewRequest("POST", c.config.GlobalpingAPIURL+"/measurements", bytes.NewBuffer(postData))
if err != nil {
return nil, false, errors.New("failed to create request - please report this bug")
}
req.Header.Set("User-Agent", userAgent())
req.Header.Set("Accept-Encoding", "br")
req.Header.Set("Content-Type", "application/json")

if c.config.GlobalpingToken != "" {
req.Header.Set("Authorization", "Bearer "+c.config.GlobalpingToken)
}

// Make the request
resp, err := c.http.Do(req)
if err != nil {
Expand Down Expand Up @@ -65,6 +63,11 @@ func (c *client) CreateMeasurement(measurement *MeasurementCreate) (*Measurement
return nil, true, fmt.Errorf("invalid parameters\n%sPlease check the help for more information", resErr)
}

// 401 error
if data.Error.Type == "unauthorized" {
return nil, false, fmt.Errorf("unauthorized: %s", data.Error.Message)
}

// 500 error
if data.Error.Type == "api_error" {
return nil, false, errors.New("internal server error - please try again later")
Expand Down Expand Up @@ -107,7 +110,7 @@ func (c *client) GetMeasurement(id string) (*Measurement, error) {
// GetMeasurementRaw returns the API response's raw json response
func (c *client) GetMeasurementRaw(id string) ([]byte, error) {
// Create a new request
req, err := http.NewRequest("GET", c.apiUrl+"/"+id, nil)
req, err := http.NewRequest("GET", c.config.GlobalpingAPIURL+"/measurements/"+id, nil)
if err != nil {
return nil, errors.New("err: failed to create request")
}
Expand Down
77 changes: 64 additions & 13 deletions globalping/globalping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"github.com/andybalholm/brotli"
"github.com/jsdelivr/globalping-cli/utils"
"github.com/jsdelivr/globalping-cli/version"

"github.com/stretchr/testify/assert"
Expand All @@ -20,6 +21,8 @@ func TestPostAPI(t *testing.T) {
os.Stdout, _ = os.Open(os.DevNull)
for scenario, fn := range map[string]func(t *testing.T){
"valid": testPostValid,
"authorized": testPostAuthorized,
"auth_error": testPostAuthorizedError,
"no_probes": testPostNoProbes,
"validation": testPostValidation,
"api_error": testPostInternalError,
Expand All @@ -34,7 +37,7 @@ func TestPostAPI(t *testing.T) {
func testPostValid(t *testing.T) {
server := generateServer(`{"id":"abcd","probesCount":1}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

opts := &MeasurementCreate{}
res, showHelp, err := client.CreateMeasurement(opts)
Expand All @@ -45,6 +48,38 @@ func testPostValid(t *testing.T) {
assert.NoError(t, err)
}

func testPostAuthorized(t *testing.T) {
server := generateServerAuthorized(`{"id":"abcd","probesCount":1}`)
defer server.Close()
client := NewClient(&utils.Config{
GlobalpingToken: "secret",
GlobalpingAPIURL: server.URL,
})

opts := &MeasurementCreate{}
res, showHelp, err := client.CreateMeasurement(opts)

assert.Equal(t, "abcd", res.ID)
assert.Equal(t, 1, res.ProbesCount)
assert.False(t, showHelp)
assert.NoError(t, err)
}

func testPostAuthorizedError(t *testing.T) {
server := generateServerAuthorized(`{"id":"abcd","probesCount":1}`)
defer server.Close()
client := NewClient(&utils.Config{
GlobalpingAPIURL: server.URL,
})

opts := &MeasurementCreate{}
res, showHelp, err := client.CreateMeasurement(opts)

assert.Nil(t, res)
assert.False(t, showHelp)
assert.EqualError(t, err, "unauthorized: Unauthorized.")
}

func testPostNoProbes(t *testing.T) {
server := generateServerError(`{
"error": {
Expand All @@ -53,7 +88,7 @@ func testPostNoProbes(t *testing.T) {
}}`, 422)
defer server.Close()

client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})
opts := &MeasurementCreate{}
_, showHelp, err := client.CreateMeasurement(opts)

Expand All @@ -72,7 +107,7 @@ func testPostValidation(t *testing.T) {
}
}}`, 400)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

opts := &MeasurementCreate{}
_, showHelp, err := client.CreateMeasurement(opts)
Expand All @@ -98,7 +133,7 @@ func testPostInternalError(t *testing.T) {
"type": "api_error"
}}`, 500)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

opts := &MeasurementCreate{}
_, showHelp, err := client.CreateMeasurement(opts)
Expand Down Expand Up @@ -126,7 +161,7 @@ func TestGetAPI(t *testing.T) {
func testGetValid(t *testing.T) {
server := generateServer(`{"id":"abcd"}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})
res, err := client.GetMeasurement("abcd")
if err != nil {
t.Error(err)
Expand All @@ -137,7 +172,7 @@ func testGetValid(t *testing.T) {
func testGetJson(t *testing.T) {
server := generateServer(`{"id":"abcd"}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})
res, err := client.GetMeasurementRaw("abcd")
if err != nil {
t.Error(err)
Expand Down Expand Up @@ -190,7 +225,7 @@ func testGetPing(t *testing.T) {
}
}]}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

res, err := client.GetMeasurement("abcd")
if err != nil {
Expand Down Expand Up @@ -286,7 +321,7 @@ func testGetTraceroute(t *testing.T) {
}}]}`)
defer server.Close()

client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

res, err := client.GetMeasurement("abcd")
if err != nil {
Expand Down Expand Up @@ -362,7 +397,7 @@ func testGetDns(t *testing.T) {
}
}]}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

res, err := client.GetMeasurement("abcd")
if err != nil {
Expand Down Expand Up @@ -484,7 +519,7 @@ func testGetMtr(t *testing.T) {
}
}]}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

res, err := client.GetMeasurement("abcd")
if err != nil {
Expand Down Expand Up @@ -589,7 +624,7 @@ func testGetHttp(t *testing.T) {
}
}]}`)
defer server.Close()
client := NewClient(server.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: server.URL})

res, err := client.GetMeasurement("abcd")
if err != nil {
Expand Down Expand Up @@ -663,7 +698,7 @@ func TestFetchWithEtag(t *testing.T) {
assert.NoError(t, err)
}))

client := NewClient(s.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: s.URL})

// first request for id1
m, err := client.GetMeasurement(id1)
Expand Down Expand Up @@ -709,7 +744,7 @@ func TestFetchWithBrotli(t *testing.T) {
assert.NoError(t, err)
}))

client := NewClient(s.URL)
client := NewClient(&utils.Config{GlobalpingAPIURL: s.URL})

m, err := client.GetMeasurement(id)
assert.NoError(t, err)
Expand All @@ -734,6 +769,22 @@ func generateServer(json string) *httptest.Server {
return server
}

func generateServerAuthorized(json string) *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer secret" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error": {"type": "unauthorized", "message": "Unauthorized."}}`))
return
}
w.WriteHeader(http.StatusAccepted)
_, err := w.Write([]byte(json))
if err != nil {
panic(err)
}
}))
return server
}

func generateServerError(json string, statusCode int) *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.4.0
golang.org/x/term v0.18.0
Expand All @@ -21,7 +22,6 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
Expand Down
23 changes: 23 additions & 0 deletions utils/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package utils

import (
"os"
_time "time"
)

type Config struct {
GlobalpingToken string
GlobalpingAPIURL string
GlobalpingAPIInterval _time.Duration
}

func NewConfig() *Config {
return &Config{
GlobalpingAPIURL: "https://api.globalping.io/v1",
GlobalpingAPIInterval: 500 * _time.Millisecond,
}
}

func (c *Config) Load() {
c.GlobalpingToken = os.Getenv("GLOBALPING_TOKEN")
}

0 comments on commit 03a8dfa

Please sign in to comment.