summaryrefslogtreecommitdiff
path: root/internal/server/api.go
diff options
context:
space:
mode:
authorAleksandar Pesic <peske.nis@gmail.com>2022-12-04 21:48:20 +0100
committerjwijenbergh <jeroenwijenbergh@protonmail.com>2022-12-12 13:26:51 +0100
commit3ac1d35257b56cca92ad0eb7f4d18abb366cf105 (patch)
tree432db14d1f92a252518f371be420fa0d3ef044c8 /internal/server/api.go
parent37bca013bd4405548b274ac473acf959ad661ee6 (diff)
simplify error handling
fixes #6 Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
Diffstat (limited to 'internal/server/api.go')
-rw-r--r--internal/server/api.go214
1 files changed, 93 insertions, 121 deletions
diff --git a/internal/server/api.go b/internal/server/api.go
index 21ba6f4..dfa8e14 100644
--- a/internal/server/api.go
+++ b/internal/server/api.go
@@ -2,7 +2,6 @@ package server
import (
"encoding/json"
- "errors"
"fmt"
"net/http"
"net/url"
@@ -10,130 +9,114 @@ import (
"time"
httpw "github.com/eduvpn/eduvpn-common/internal/http"
- "github.com/eduvpn/eduvpn-common/types"
+ "github.com/go-errors/errors"
)
func APIGetEndpoints(baseURL string) (*Endpoints, error) {
- errorMessage := "failed getting server endpoints"
- url, urlErr := url.Parse(baseURL)
- if urlErr != nil {
- return nil, types.NewWrappedError(errorMessage, urlErr)
+ u, err := url.Parse(baseURL)
+ if err != nil {
+ return nil, errors.WrapPrefix(err, "failed getting server endpoints", 0)
}
- wellKnownPath := "/.well-known/vpn-user-portal"
+ wk := "/.well-known/vpn-user-portal"
- url.Path = path.Join(url.Path, wellKnownPath)
- _, body, bodyErr := httpw.Get(url.String())
-
- if bodyErr != nil {
- return nil, types.NewWrappedError(errorMessage, bodyErr)
+ u.Path = path.Join(u.Path, wk)
+ _, body, err := httpw.Get(u.String())
+ if err != nil {
+ return nil, errors.WrapPrefix(err, "failed getting server endpoints", 0)
}
- endpoints := &Endpoints{}
- jsonErr := json.Unmarshal(body, endpoints)
-
- if jsonErr != nil {
- return nil, types.NewWrappedError(errorMessage, jsonErr)
+ ep := &Endpoints{}
+ if err = json.Unmarshal(body, ep); err != nil {
+ return nil, errors.WrapPrefix(err, "failed getting server endpoints", 0)
}
- return endpoints, nil
+ return ep, nil
}
func apiAuthorized(
- server Server,
+ srv Server,
method string,
endpoint string,
opts *httpw.OptionalParams,
) (http.Header, []byte, error) {
- errorMessage := "failed API authorized"
// Ensure optional is not nil as we will fill it with headers
if opts == nil {
opts = &httpw.OptionalParams{}
}
- base, baseErr := server.Base()
-
- if baseErr != nil {
- return nil, nil, types.NewWrappedError(errorMessage, baseErr)
+ b, err := srv.Base()
+ if err != nil {
+ return nil, nil, errors.WrapPrefix(err, "failed API authorized", 0)
}
// Join the paths
- url, urlErr := url.Parse(base.Endpoints.API.V3.API)
- if urlErr != nil {
- return nil, nil, types.NewWrappedError(errorMessage, urlErr)
+ u, err := url.Parse(b.Endpoints.API.V3.API)
+ if err != nil {
+ return nil, nil, errors.WrapPrefix(err, "failed API authorized", 0)
}
- url.Path = path.Join(url.Path, endpoint)
+ u.Path = path.Join(u.Path, endpoint)
// Make sure the tokens are valid, this will return an error if re-login is needed
- token, tokenErr := HeaderToken(server)
- if tokenErr != nil {
- return nil, nil, types.NewWrappedError(errorMessage, tokenErr)
+ t, err := HeaderToken(srv)
+ if err != nil {
+ return nil, nil, errors.WrapPrefix(err, "failed API authorized", 0)
}
- headerKey := "Authorization"
- headerValue := fmt.Sprintf("Bearer %s", token)
+ key := "Authorization"
+ val := fmt.Sprintf("Bearer %s", t)
if opts.Headers != nil {
- opts.Headers.Add(headerKey, headerValue)
+ opts.Headers.Add(key, val)
} else {
- opts.Headers = http.Header{headerKey: {headerValue}}
+ opts.Headers = http.Header{key: {val}}
}
- return httpw.MethodWithOpts(method, url.String(), opts)
+ return httpw.MethodWithOpts(method, u.String(), opts)
}
func apiAuthorizedRetry(
- server Server,
+ srv Server,
method string,
endpoint string,
opts *httpw.OptionalParams,
) (http.Header, []byte, error) {
- errorMessage := "failed authorized API retry"
- header, body, bodyErr := apiAuthorized(server, method, endpoint, opts)
-
- if bodyErr != nil {
- var error *httpw.StatusError
-
- // Only retry authorized if we get a HTTP 401
- if errors.As(bodyErr, &error) && error.Status == 401 {
- // Mark the token as expired and retry so we trigger the refresh flow
- MarkTokenExpired(server)
- retryHeader, retryBody, retryErr := apiAuthorized(server, method, endpoint, opts)
- if retryErr != nil {
- return nil, nil, types.NewWrappedError(errorMessage, retryErr)
- }
- return retryHeader, retryBody, nil
- }
- return nil, nil, types.NewWrappedError(errorMessage, bodyErr)
- }
- return header, body, nil
-}
-
-func APIInfo(server Server) error {
- errorMessage := "failed API /info"
- _, body, bodyErr := apiAuthorizedRetry(server, http.MethodGet, "/info", nil)
- if bodyErr != nil {
- return types.NewWrappedError(errorMessage, bodyErr)
+ h, body, err := apiAuthorized(srv, method, endpoint, opts)
+ if err == nil {
+ return h, body, nil
}
- structure := ProfileInfo{}
- jsonErr := json.Unmarshal(body, &structure)
- if jsonErr != nil {
- return types.NewWrappedError(errorMessage, jsonErr)
+ statErr := &httpw.StatusError{}
+ // Only retry authorized if we get an HTTP 401
+ if errors.As(err, &statErr) && statErr.Status == 401 {
+ // Mark the token as expired and retry, so we trigger the refresh flow
+ MarkTokenExpired(srv)
+ h, body, err = apiAuthorized(srv, method, endpoint, opts)
}
+ return h, body, err
+}
- base, baseErr := server.Base()
+func APIInfo(srv Server) error {
+ _, body, err := apiAuthorizedRetry(srv, http.MethodGet, "/info", nil)
+ if err != nil {
+ return err
+ }
+ pi := ProfileInfo{}
+ if err = json.Unmarshal(body, &pi); err != nil {
+ return errors.WrapPrefix(err, "failed API /info", 0)
+ }
- if baseErr != nil {
- return types.NewWrappedError(errorMessage, baseErr)
+ b, err := srv.Base()
+ if err != nil {
+ return err
}
// Store the profiles and make sure that the current profile is not overwritten
- previousProfile := base.Profiles.Current
- base.Profiles = structure
- base.Profiles.Current = previousProfile
+ prev := b.Profiles.Current
+ b.Profiles = pi
+ b.Profiles.Current = prev
return nil
}
// see https://github.com/eduvpn/documentation/blob/v3/API.md#request-1
-func GetPreferTCPString(preferTCP bool) string {
+func boolToYesNo(preferTCP bool) string {
if preferTCP {
return "yes"
}
@@ -141,88 +124,77 @@ func GetPreferTCPString(preferTCP bool) string {
}
func APIConnectWireguard(
- server Server,
+ srv Server,
profileID string,
pubkey string,
preferTCP bool,
- supportsOpenVPN bool,
+ openVPNSupport bool,
) (string, string, time.Time, error) {
- errorMessage := "failed obtaining a WireGuard configuration"
- headers := http.Header{
+ hdrs := http.Header{
"content-type": {"application/x-www-form-urlencoded"},
"accept": {"application/x-wireguard-profile"},
}
// This profile also supports OpenVPN
// Indicate that we also accept OpenVPN profiles
- if supportsOpenVPN {
- headers.Add("accept", "application/x-openvpn-profile")
+ if openVPNSupport {
+ hdrs.Add("accept", "application/x-openvpn-profile")
}
- urlForm := url.Values{
+ vals := url.Values{
"profile_id": {profileID},
"public_key": {pubkey},
- "prefer_tcp": {GetPreferTCPString(preferTCP)},
+ "prefer_tcp": {boolToYesNo(preferTCP)},
}
- header, connectBody, connectErr := apiAuthorizedRetry(
- server,
- http.MethodPost,
- "/connect",
- &httpw.OptionalParams{Headers: headers, Body: urlForm},
- )
- if connectErr != nil {
- return "", "", time.Time{}, types.NewWrappedError(
- errorMessage,
- connectErr,
- )
+ h, body, err := apiAuthorizedRetry(srv, http.MethodPost, "/connect",
+ &httpw.OptionalParams{Headers: hdrs, Body: vals})
+ if err != nil {
+ return "", "", time.Time{}, err
}
- expires := header.Get("expires")
+ exp := h.Get("expires")
- pTime, pTimeErr := http.ParseTime(expires)
- if pTimeErr != nil {
- return "", "", time.Time{}, types.NewWrappedError(errorMessage, pTimeErr)
+ ptm, err := http.ParseTime(exp)
+ if err != nil {
+ return "", "", time.Time{}, errors.WrapPrefix(err, "failed obtaining a WireGuard configuration", 0)
}
- contentType := header.Get("content-type")
-
- content := "openvpn"
- if contentType == "application/x-wireguard-profile" {
- content = "wireguard"
+ ct := h.Get("content-type")
+ c := "openvpn"
+ if ct == "application/x-wireguard-profile" {
+ c = "wireguard"
}
- return string(connectBody), content, pTime, nil
+
+ return string(body), c, ptm, nil
}
-func APIConnectOpenVPN(server Server, profileID string, preferTCP bool) (string, time.Time, error) {
- errorMessage := "failed obtaining an OpenVPN configuration"
- headers := http.Header{
+func APIConnectOpenVPN(srv Server, profileID string, preferTCP bool) (string, time.Time, error) {
+ hdrs := http.Header{
"content-type": {"application/x-www-form-urlencoded"},
"accept": {"application/x-openvpn-profile"},
}
- urlForm := url.Values{
+ vals := url.Values{
"profile_id": {profileID},
- "prefer_tcp": {GetPreferTCPString(preferTCP)},
+ "prefer_tcp": {boolToYesNo(preferTCP)},
}
- header, connectBody, connectErr := apiAuthorizedRetry(
- server,
- http.MethodPost,
- "/connect",
- &httpw.OptionalParams{Headers: headers, Body: urlForm},
- )
- if connectErr != nil {
- return "", time.Time{}, types.NewWrappedError(errorMessage, connectErr)
+ h, body, err := apiAuthorizedRetry(srv, http.MethodPost, "/connect",
+ &httpw.OptionalParams{Headers: hdrs, Body: vals})
+ if err != nil {
+ return "", time.Time{}, err
}
- expires := header.Get("expires")
- pTime, pTimeErr := http.ParseTime(expires)
- if pTimeErr != nil {
- return "", time.Time{}, types.NewWrappedError(errorMessage, pTimeErr)
+ exp := h.Get("expires")
+ ptm, err := http.ParseTime(exp)
+ if err != nil {
+ return "", time.Time{}, errors.WrapPrefix(err, "failed obtaining an OpenVPN configuration", 0)
}
- return string(connectBody), pTime, nil
+
+ return string(body), ptm, nil
}
+// APIDisconnect disconnects from the API.
// This needs no further return value as it's best effort.
func APIDisconnect(server Server) {
_, _, _ = apiAuthorized(server, http.MethodPost, "/disconnect", nil)