goesi

package module
v0.0.0-...-035b283 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2023 License: MIT Imports: 18 Imported by: 0

README

GoESI "Go Easy" API client for esi

ko-fi

An OpenAPI for EVE Online ESI API

A module to allow access to CCP's EVE Online ESI API. This module offers:

  • Versioned Endpoints
  • OAuth2 authentication to login.eveonline.com
  • Handle many tokens, with different scopes.
  • 100% ESI API coverage.
  • context.Context passthrough (for httptrace, logging, etc).

Installation

    go get github.com/contorno/goesi

New Client

client := goesi.NewAPIClient(&http.Client, "MyApp (someone@somewhere.com dude on slack)")

One client should be created that will serve as an agent for all requests. This allows http2 multiplexing and keep-alive be used to optimize connections. It is also good manners to provide a user-agent describing the point of use of the API, allowing CCP to contact you in case of emergencies.

Example:

package main

import (
	"context"
	"fmt"

	"github.com/contorno/goesi"
)

func main() {
	// create ESI client
	client := goesi.NewAPIClient(nil, "name@example.com")
	// call Status endpoint
	status, _, err := client.ESI.StatusApi.GetStatus(context.Background(), nil)
	if err != nil {
		panic(err)
	}
	// print current status
	fmt.Println("Players online: ", status.Players)
}

Etiquette

Obeying the Cache Times

Caching is not implimented by the client and thus it is required to utilize a caching http client. It is highly recommended to utilize a client capable of caching the entire cluster of API clients.

An example using gregjones/httpcache and memcache:

import (
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/gregjones/httpcache"
	httpmemcache "github.com/gregjones/httpcache/memcache"
)

func main() {
	// Connect to the memcache server
	cache := memcache.New(MemcachedAddresses...)

	// Create a memcached http client for the CCP APIs.
	transport := httpcache.NewTransport(httpmemcache.NewWithClient(cache))
	transport.Transport = &http.Transport{Proxy: http.ProxyFromEnvironment}
	client = &http.Client{Transport: transport}

	// Get our API Client.
	eve := goesi.NewAPIClient(client, "My user agent, contact somewhere@nowhere")
}

ETags

You should support using ETags if you are requesting data that is frequently not changed. IF you are using httpcache, it supports etags already. If you are not using a cache middleware, you will want to create your own middleware like this.

package myetagpackage
type contextKey string

func (c contextKey) String() string {
	return "mylib " + string(c)
}

// ContextETag is the context to pass etags to the transport
var (
	ContextETag = contextKey("etag")
)

// Custom transport to chain into the HTTPClient to gather statistics.
type ETagTransport struct {
	Next *http.Transport
}

// RoundTrip wraps http.DefaultTransport.RoundTrip to provide stats and handle error rates.
func (t *ETagTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if etag, ok := req.Context().Value(ContextETag).(string); ok {
		req.Header.Set("if-none-match", etag)
	}

	// Run the request.
	return t.Next.RoundTrip(req)
}

This is then looped in the transport, and passed through a context like so:

func main() {
	// Loop in our middleware
	client := &http.Client{Transport: &ETagTransport{Next: &http.Transport{}}}

	// Make a new client with the middleware
	esiClient := goesi.NewAPIClient(client, "MyApp (someone@somewhere.com dude on slack)")

	// Make a request with the context
	ctx := context.WithValue(context.Background(), myetagpackage.ContextETag, "etag goes here")
	regions, _, err := esiClient.UniverseApi.GetUniverseRegions(ctx, nil)
	if err != nil {
		return err
	}
}

Authenticating

Register your application at https://developers.eveonline.com/ to get your secretKey, clientID, and scopes.

Obtaining tokens for client requires two HTTP handlers. One to generate and redirect to the SSO URL, and one to receive the response.

It is mandatory to create a random state and compare this state on return to prevent token injection attacks on the application.

pseudocode example:


func main() {
var err error
ctx := appContext.AppContext{}
ctx.ESI = goesi.NewAPIClient(httpClient, "My App, contact someone@nowhere")
ctx.SSOAuthenticator = goesi.NewSSOAuthenticator(httpClient, clientID, secretKey, scopes)
}

func eveSSO(c *appContext.AppContext, w http.ResponseWriter, r *http.Request,
	s *sessions.Session) (int, error) {

	// Generate a random state string
	b := make([]byte, 16)
	rand.Read(b)
	state := base64.URLEncoding.EncodeToString(b)

	// Save the state on the session
	s.Values["state"] = state
	err := s.Save(r, w)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Generate the SSO URL with the state string
	url := c.SSOAuthenticator.AuthorizeURL(state, true)

	// Send the user to the URL
	http.Redirect(w, r, url, 302)
	return http.StatusMovedPermanently, nil
}

func eveSSOAnswer(c *appContext.AppContext, w http.ResponseWriter, r *http.Request,
	s *sessions.Session) (int, error) {

	// get our code and state
	code := r.FormValue("code")
	state := r.FormValue("state")

	// Verify the state matches our randomly generated string from earlier.
	if s.Values["state"] != state {
		return http.StatusInternalServerError, errors.New("Invalid State.")
	}

	// Exchange the code for an Access and Refresh token.
	token, err := c.SSOAuthenticator.TokenExchange(code)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Obtain a token source (automaticlly pulls refresh as needed)
	tokSrc, err := c.SSOAuthenticator.TokenSource(tok)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Assign an auth context to the calls
	auth := context.WithValue(context.TODO(), goesi.ContextOAuth2, tokSrc.Token)

	// Verify the client (returns clientID)
	v, err := c.SSOAuthenticator.Verify(auth)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Save the verification structure on the session for quick access.
	s.Values["character"] = v
	err = s.Save(r, w)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Redirect to the account page.
	http.Redirect(w, r, "/account", 302)
	return http.StatusMovedPermanently, nil
}

Passing Tokens

OAuth2 tokens are passed to endpoints via contexts. Example:

	ctx := context.WithValue(context.Background(), goesi.ContextOAuth2, ESIPublicToken)
	struc, response, err := client.V1.UniverseApi.GetUniverseStructuresStructureId(ctx, structureID, nil)

This is done here rather than at the client so you can use one client for many tokens, saving connections.

Testing

If you would rather not rely on public ESI for testing, a mock ESI server is available for local and CI use. Information here: https://github.com/contorno/mock-esi

What about the other stuff?

If you need bleeding edge access, add the endpoint to the generator and rebuild this module. Generator is here: https://github.com/contorno/swagger-esi-goclient

Documentation for API Endpoints

ESI Endpoints

Author

contorno on #devfleet slack

Credits

https://github.com/go-resty/resty (MIT license) Copyright © 2015-2016 Jeevanandam M (jeeva@myjeeva.com)

  • Uses modified setBody and detectContentType

https://github.com/gregjones/httpcache (MIT license) Copyright © 2012 Greg Jones (greg.jones@gmail.com)

  • Uses parseCacheControl and CacheExpires as a helper function

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ContextOAuth2      = esi.ContextOAuth2
	ContextAccessToken = esi.ContextAccessToken
)

ContextOAuth2 is the context for GoESI authentication. Pass a tokenSource with this key to a context for an ESI API Call.

View Source
var FactionAllies = map[int32]int32{
	500001: 500003,
	500003: 500001,
	500002: 500004,
	500004: 500002,
}

FactionAllies resolves friendly faction war IDs

View Source
var FactionsAtWar = map[int32][]int32{
	500001: {500002, 500004},
	500003: {500002, 500004},
	500002: {500001, 500003},
	500004: {500001, 500003},
}

FactionsAtWar resolves two enemy parties for each factionID

View Source
var FactionsByID = map[int32]string{500001: "Caldari", 500002: "Minmatar", 500003: "Amarr", 500004: "Gallente"}

FactionsByID Factions resolves faction ID to Name

View Source
var FactionsByName = map[string]int32{"Caldari": 500001, "Minmatar": 500002, "Amarr": 500003, "Gallente": 500004}

FactionsByName Factions resolves faction name to ID

View Source
var JournalRefID = map[string]int{}/* 176 elements not displayed */

JournalRefID Maps journal strings to CCP internal refID. CCP has stated these are subject to change. Please submit pull requests for new IDs

Functions

func CacheExpires

func CacheExpires(r *http.Response) time.Time

CacheExpires helper function to determine remaining time before repeating a request.

func FactionNameToID

func FactionNameToID(faction string) int32

func GetJournalRefID

func GetJournalRefID(referenceName string) int

GetJournalRefID looks up the Journal reference name and returns the internal ID WARNING: These are subject to change per CCP.

func ParseTime

func ParseTime(input int64) time.Time

func TokenFromJSON

func TokenFromJSON(jsonStr string) (*oauth2.Token, error)

TokenFromJSON helper function to convert stored JSON to a token.

func TokenToJSON

func TokenToJSON(token *oauth2.Token) (string, error)

TokenToJSON helper function to convert a token to a storable format.

Types

type APIClient

type APIClient struct {
	ESI  *esi.APIClient
	Meta *meta.APIClient
}

APIClient manages communication with the EVE Swagger Interface API In most cases there should be only one, shared, APIClient.

func NewAPIClient

func NewAPIClient(httpClient *http.Client, userAgent string) *APIClient

NewAPIClient creates a new API client. Requires a userAgent string describing your application. optionally a custom http.Client to allow for advanced features such as caching.

func (*APIClient) ChangeBasePath

func (c *APIClient) ChangeBasePath(path string)

ChangeBasePath allows alternate ESI paths to be used for testing

type EVESSOClaims

type EVESSOClaims struct {
	Name     string   `json:"name,omitempty"`
	Owner    string   `json:"owner,omitempty"`
	Scopes   []string `json:"scp,omitempty"`
	ClientID string   `json:"azp,omitempty"`
	Tenant   string   `json:"tenant,omitempty"`
	Tier     string   `json:"tier,omitempty"`
	Region   string   `json:"region,omitempty"`
	jwt.RegisteredClaims
}

EVESSOClaims JWT Claims.

type SSOAuthenticator

type SSOAuthenticator struct {
	// contains filtered or unexported fields
}

SSOAuthenticator interface for the EVE SSO. SSOAuthenticator [TODO] lose this mutex and allow scopes to change without conflict.

func NewSSOAuthenticator

func NewSSOAuthenticator(
	client *http.Client, clientID string, clientSecret string, redirectURL string, scopes []string,
) *SSOAuthenticator

func (*SSOAuthenticator) AuthorizeURL

func (c *SSOAuthenticator) AuthorizeURL(state string, onlineAccess bool, scopes []string) string

AuthorizeURL returns an url for an end user to authenticate with EVE SSO and return success to the redirectURL. It is important to create a significantly unique state for this request and verify the state matches when returned to the redirectURL.

func (*SSOAuthenticator) ChangeAuthURL

func (c *SSOAuthenticator) ChangeAuthURL(authUrl string)

ChangeAuthURL changes the oauth2 configuration url for authentication

func (*SSOAuthenticator) ChangeTokenURL

func (c *SSOAuthenticator) ChangeTokenURL(tokenUrl string)

ChangeTokenURL changes the oauth2 configuration url for token

func (*SSOAuthenticator) TokenExchange

func (c *SSOAuthenticator) TokenExchange(code string) (*oauth2.Token, error)

TokenExchange exchanges the code returned to the redirectURL for an access token. A caching client must be passed. This client MUST cache per CCP guidelines or face banning.

func (*SSOAuthenticator) TokenRevoke

func (c *SSOAuthenticator) TokenRevoke(refreshToken string) error

TokenRevoke revokes a refresh token

func (*SSOAuthenticator) TokenSource

func (c *SSOAuthenticator) TokenSource(token *oauth2.Token) oauth2.TokenSource

TokenSource creates a refreshable token that can be passed to ESI functions

func (*SSOAuthenticator) Verify

Verify the client and collect user information.

type VerifyResponse

type VerifyResponse struct {
	CharacterID        int32
	CharacterName      string
	ExpiresOn          string
	Scopes             string
	TokenType          string
	CharacterOwnerHash string
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL