Skip to content

Commit

Permalink
pkg/console.go: add support for new console endpoints
Browse files Browse the repository at this point in the history
The console endpoints used to get/create projects, as well as API keys
has changed from graphQL to a new json http endpoint. This change
adds support for the new endpoint, while maintaining compatibility
with satellites that still use the old endpoints.

Change-Id: I043be3bd4a0b056f0dec7f54e4046887931c9ed8
  • Loading branch information
dlamarmorgan committed Aug 23, 2023
1 parent 057b4a9 commit d61cfc9
Showing 1 changed file with 184 additions and 8 deletions.
192 changes: 184 additions & 8 deletions pkg/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
Expand Down Expand Up @@ -242,33 +243,75 @@ func (ce *ConsoleEndpoint) activateUser(ctx context.Context, userID string, emai
}

// GetOrCreateProject return with project to use in this session.
// this method will try to get/create the project using the old graphql endpoint,
// and if it fails, it will try to get/create the project using the new http endpoint.
// see the changes in https://github.com/storj/storj/commit/516241e406923dedcc66df06b7e7c1479dc98b91
// for the update from old API to new.
// TODO: update this method when the old API is no longer needed.
func (ce *ConsoleEndpoint) GetOrCreateProject(ctx context.Context) (string, string, error) {
projectID, token, err := ce.getProject(ctx)
projectID, token, err := ce.getGraphqlProject(ctx)
if errors.Is(err, io.EOF) {
projectID, token, err = ce.getHttpProject(ctx)
}
if err == nil {
return projectID, token, nil
}
projectID, token, err = ce.createProject(ctx)
projectID, token, err = ce.createGraphqlProject(ctx)
if errors.Is(err, io.EOF) {
projectID, token, err = ce.createHttpProject(ctx)
}
if err == nil {
return projectID, token, nil
}
return ce.getProject(ctx)
return "", "", err
}

func (ce *ConsoleEndpoint) getProject(ctx context.Context) (string, string, error) {
func (ce *ConsoleEndpoint) getHttpProject(ctx context.Context) (string, string, error) {
var projects []struct {
ID string `json:"id"`
}
err := ce.projectQuery(ctx, &projects)
if err != nil {
return "", "", err
}
if len(projects) == 0 {
return "", "", errs.New("No project exists")
}
return projects[0].ID, ce.token, nil
}

func (ce *ConsoleEndpoint) createHttpProject(ctx context.Context) (string, string, error) {
rng := rand.NewSource(time.Now().UnixNano())
body := fmt.Sprintf(`{"name":"TestProject-%d","description":""}`, rng.Int63())

var createdProject struct {
ID string `json:"id"`
}
err := ce.projectMutation(ctx, body, &createdProject)
if err != nil {
return "", "", err
}
return createdProject.ID, ce.token, nil
}

func (ce *ConsoleEndpoint) getGraphqlProject(ctx context.Context) (string, string, error) {
query := `query {myProjects{id}}`
var getProjects struct {
MyProjects []struct {
ID string
}
}
err := ce.graphqlQuery(ctx, query, &getProjects)
if err != nil {
return "", "", err
}
if len(getProjects.MyProjects) == 0 {
return "", "", errs.New("No project exists")
}
return getProjects.MyProjects[0].ID, ce.token, err
return getProjects.MyProjects[0].ID, ce.token, nil
}

func (ce *ConsoleEndpoint) createProject(ctx context.Context) (string, string, error) {
func (ce *ConsoleEndpoint) createGraphqlProject(ctx context.Context) (string, string, error) {
rng := rand.NewSource(time.Now().UnixNano())
createProjectQuery := fmt.Sprintf(
`mutation {createProject(input:{name:"TestProject-%d",description:""}){id}}`,
Expand All @@ -280,11 +323,27 @@ func (ce *ConsoleEndpoint) createProject(ctx context.Context) (string, string, e
}
}
err := ce.graphqlMutation(ctx, createProjectQuery, &createProject)
return createProject.CreateProject.ID, ce.token, err
if err != nil {
return "", "", err
}
return createProject.CreateProject.ID, ce.token, nil
}

// CreateAPIKey creates new API key to access Storj services.
// this method will try to create the key using the old graphql endpoint,
// and if it fails, it will try to get the project using the new http endpoint.
// see the changes in https://github.com/storj/storj/commit/516241e406923dedcc66df06b7e7c1479dc98b91
// for the update from old API to new.
// TODO: update this method when the old API is no longer needed.
func (ce *ConsoleEndpoint) CreateAPIKey(ctx context.Context, projectID string) (string, error) {
key, err := ce.createGraphqlAPIKey(ctx, projectID)
if errors.Is(err, io.EOF) {
key, err = ce.createAPIKey(ctx, projectID)
}
return key, err
}

func (ce *ConsoleEndpoint) createGraphqlAPIKey(ctx context.Context, projectID string) (string, error) {
rng := rand.NewSource(time.Now().UnixNano())
createAPIKeyQuery := fmt.Sprintf(
`mutation {createAPIKey(projectID:%q,name:"TestKey-%d"){key}}`,
Expand All @@ -296,7 +355,84 @@ func (ce *ConsoleEndpoint) CreateAPIKey(ctx context.Context, projectID string) (
}
}
err := ce.graphqlMutation(ctx, createAPIKeyQuery, &createAPIKey)
return createAPIKey.CreateAPIKey.Key, err
if err != nil {
return "", err
}
return createAPIKey.CreateAPIKey.Key, nil
}

func (ce *ConsoleEndpoint) createAPIKey(ctx context.Context, projectID string) (string, error) {
rng := rand.NewSource(time.Now().UnixNano())
apiKeyName := fmt.Sprintf("TestKey-%d", rng.Int63())

var createdKey struct {
Key string `json:"key"`
}
err := ce.apiKeyMutation(ctx, apiKeyName, projectID, &createdKey)
if err != nil {
return "", err
}
return createdKey.Key, nil
}

func (ce *ConsoleEndpoint) projectQuery(ctx context.Context, response interface{}) error {
request, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
ce.projectEndpointPath(),
nil)
if err != nil {
return errs.Wrap(err)
}

request.AddCookie(&http.Cookie{
Name: ce.cookieName,
Value: ce.token,
})

request.Header.Add("Content-Type", "application/json")

return ce.httpDo(request, response)
}

func (ce *ConsoleEndpoint) projectMutation(ctx context.Context, query string, response interface{}) error {
request, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
ce.projectEndpointPath(),
bytes.NewReader([]byte(query)))
if err != nil {
return errs.Wrap(err)
}

request.AddCookie(&http.Cookie{
Name: ce.cookieName,
Value: ce.token,
})

request.Header.Add("Content-Type", "application/json")

return ce.httpDo(request, response)
}

func (ce *ConsoleEndpoint) apiKeyMutation(ctx context.Context, apiKeyName, projectID string, response interface{}) error {
request, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
ce.apiKeyEndpointPath()+"/create/"+projectID,
bytes.NewReader([]byte(apiKeyName)))
if err != nil {
return errs.Wrap(err)
}

request.AddCookie(&http.Cookie{
Name: ce.cookieName,
Value: ce.token,
})

request.Header.Add("Content-Type", "application/json")

return ce.httpDo(request, response)
}

func (ce *ConsoleEndpoint) graphqlQuery(ctx context.Context, createAPIKeyQuery string, response interface{}) error {
Expand Down Expand Up @@ -409,6 +545,38 @@ func (ce *ConsoleEndpoint) graphqlDo(request *http.Request, jsonResponse interfa
return json.NewDecoder(bytes.NewReader(response.Data)).Decode(jsonResponse)
}

func (ce *ConsoleEndpoint) httpDo(request *http.Request, jsonResponse interface{}) error {
resp, err := ce.client.Do(request)
if err != nil {
return err
}
defer func() { err = errs.Combine(err, resp.Body.Close()) }()

b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

if jsonResponse == nil {
return errs.New("empty response: %q", b)
}

if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return json.NewDecoder(bytes.NewReader(b)).Decode(jsonResponse)
}

var errResponse struct {
Error string `json:"error"`
}

err = json.NewDecoder(bytes.NewReader(b)).Decode(&errResponse)
if err != nil {
return err
}

return errs.New("request failed with status %d: %s", resp.StatusCode, errResponse.Error)
}

func (ce *ConsoleEndpoint) appendPath(suffix string) string {
return ce.base + suffix
}
Expand All @@ -433,6 +601,14 @@ func (ce *ConsoleEndpoint) graphQLEndpointPath() string {
return ce.appendPath("/api/v0/graphql")
}

func (ce *ConsoleEndpoint) projectEndpointPath() string {
return ce.appendPath("/api/v0/projects")
}

func (ce *ConsoleEndpoint) apiKeyEndpointPath() string {
return ce.appendPath("/api/v0/api-keys")
}

// RegisterAccess creates new access registered to linksharing.
func RegisterAccess(ctx context.Context, authService string, accessSerialized string) (accessKey, secretKey, endpoint string, err error) {
if authService == "" {
Expand Down

0 comments on commit d61cfc9

Please sign in to comment.