516241e406
This change removes unused GraphQL code. It also updates storj sim code to use the GraphQL replacement HTTP endpoints and removes the GraphQL dependency. Issue: https://github.com/storj/storj/issues/6142 Change-Id: Ie502553706c4b1282cd883a9275ea7332b8fc92d
590 lines
13 KiB
Go
590 lines
13 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/common/uuid"
|
|
"storj.io/storj/satellite/console/consoleauth"
|
|
"storj.io/storj/satellite/payments"
|
|
)
|
|
|
|
func newConsoleEndpoints(address string) *consoleEndpoints {
|
|
return &consoleEndpoints{
|
|
client: http.DefaultClient,
|
|
base: "http://" + address,
|
|
cookieName: "_tokenKey",
|
|
}
|
|
}
|
|
|
|
type consoleEndpoints struct {
|
|
client *http.Client
|
|
base string
|
|
cookieName string
|
|
}
|
|
|
|
func (ce *consoleEndpoints) appendPath(suffix string) string {
|
|
return ce.base + suffix
|
|
}
|
|
|
|
func (ce *consoleEndpoints) RegToken() string {
|
|
return ce.appendPath("/registrationToken/?projectsLimit=1")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) Register() string {
|
|
return ce.appendPath("/api/v0/auth/register")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) SetupAccount() string {
|
|
return ce.appendPath("/api/v0/payments/account")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) CreditCards() string {
|
|
return ce.appendPath("/api/v0/payments/cards")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) Activation(token string) string {
|
|
return ce.appendPath("/activation?token=" + token)
|
|
}
|
|
|
|
func (ce *consoleEndpoints) Token() string {
|
|
return ce.appendPath("/api/v0/auth/token")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) Projects() string {
|
|
return ce.appendPath("/api/v0/projects")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) APIKeys() string {
|
|
return ce.appendPath("/api/v0/api-keys")
|
|
}
|
|
|
|
func (ce *consoleEndpoints) 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 *consoleEndpoints) createOrGetAPIKey(ctx context.Context) (string, error) {
|
|
authToken, err := ce.tryLogin(ctx)
|
|
if err != nil {
|
|
_ = ce.tryCreateAndActivateUser(ctx)
|
|
authToken, err = ce.tryLogin(ctx)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
}
|
|
|
|
err = ce.setupAccount(ctx, authToken)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
err = ce.addCreditCard(ctx, authToken, "test")
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
cards, err := ce.listCreditCards(ctx, authToken)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
if len(cards) == 0 {
|
|
return "", errs.New("no credit card(s) found")
|
|
}
|
|
|
|
err = ce.makeCreditCardDefault(ctx, authToken, cards[0].ID)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
projectID, err := ce.getOrCreateProject(ctx, authToken)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
apiKey, err := ce.createAPIKey(ctx, authToken, projectID)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
return apiKey, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) tryLogin(ctx context.Context) (string, error) {
|
|
var authToken struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
authToken.Email = "alice@mail.test"
|
|
authToken.Password = "123a123"
|
|
|
|
res, err := json.Marshal(authToken)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.Token(),
|
|
bytes.NewReader(res))
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request.Header.Add("Content-Type", "application/json")
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
var tokenInfo struct {
|
|
Token string `json:"token"`
|
|
}
|
|
err = json.NewDecoder(resp.Body).Decode(&tokenInfo)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
return tokenInfo.Token, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) tryCreateAndActivateUser(ctx context.Context) error {
|
|
regToken, err := ce.createRegistrationToken(ctx)
|
|
if err != nil {
|
|
return errs.Wrap(err)
|
|
}
|
|
userID, err := ce.createUser(ctx, regToken)
|
|
if err != nil {
|
|
return errs.Wrap(err)
|
|
}
|
|
return errs.Wrap(ce.activateUser(ctx, userID))
|
|
}
|
|
|
|
func (ce *consoleEndpoints) createRegistrationToken(ctx context.Context) (string, error) {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodGet,
|
|
ce.RegToken(),
|
|
nil)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
var createTokenResponse struct {
|
|
Secret string
|
|
Error string
|
|
}
|
|
if err = json.NewDecoder(resp.Body).Decode(&createTokenResponse); err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
if createTokenResponse.Error != "" {
|
|
return "", errs.New("unable to create registration token: %s", createTokenResponse.Error)
|
|
}
|
|
|
|
return createTokenResponse.Secret, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) createUser(ctx context.Context, regToken string) (string, error) {
|
|
var registerData struct {
|
|
FullName string `json:"fullName"`
|
|
ShortName string `json:"shortName"`
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Secret string `json:"secret"`
|
|
}
|
|
|
|
registerData.FullName = "Alice"
|
|
registerData.Email = "alice@mail.test"
|
|
registerData.Password = "123a123"
|
|
registerData.ShortName = "al"
|
|
registerData.Secret = regToken
|
|
|
|
res, err := json.Marshal(registerData)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.Register(),
|
|
bytes.NewReader(res))
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
request.Header.Add("Content-Type", "application/json")
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
var userID string
|
|
if err = json.NewDecoder(resp.Body).Decode(&userID); err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
return userID, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) activateUser(ctx context.Context, userID string) error {
|
|
userUUID, err := uuid.FromString(userID)
|
|
if err != nil {
|
|
return errs.Wrap(err)
|
|
}
|
|
|
|
activationToken, err := generateActivationKey(userUUID, "alice@mail.test", time.Now())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodGet,
|
|
ce.Activation(activationToken),
|
|
nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) setupAccount(ctx context.Context, token string) error {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.SetupAccount(),
|
|
nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) addCreditCard(ctx context.Context, token, cctoken string) error {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.CreditCards(),
|
|
strings.NewReader(cctoken))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) listCreditCards(ctx context.Context, token string) ([]payments.CreditCard, error) {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodGet,
|
|
ce.CreditCards(),
|
|
nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
var list []payments.CreditCard
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) makeCreditCardDefault(ctx context.Context, token, ccID string) error {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPatch,
|
|
ce.CreditCards(),
|
|
strings.NewReader(ccID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
resp, err := ce.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return errs.New("unexpected status code: %d (%q)",
|
|
resp.StatusCode, tryReadLine(resp.Body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) getOrCreateProject(ctx context.Context, token string) (string, error) {
|
|
projectID, err := ce.getProject(ctx, token)
|
|
if err == nil {
|
|
return projectID, nil
|
|
}
|
|
projectID, err = ce.createProject(ctx, token)
|
|
if err == nil {
|
|
return projectID, nil
|
|
}
|
|
return ce.getProject(ctx, token)
|
|
}
|
|
|
|
func (ce *consoleEndpoints) getProject(ctx context.Context, token string) (string, error) {
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodGet,
|
|
ce.Projects(),
|
|
nil)
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
request.Header.Add("Content-Type", "application/json")
|
|
|
|
var projects []struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := ce.httpDo(request, &projects); err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
if len(projects) == 0 {
|
|
return "", errs.New("no projects")
|
|
}
|
|
|
|
return projects[0].ID, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) createProject(ctx context.Context, token string) (string, error) {
|
|
rng := rand.NewSource(time.Now().UnixNano())
|
|
body := fmt.Sprintf(`{"name":"TestProject-%d","description":""}`, rng.Int63())
|
|
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.Projects(),
|
|
bytes.NewReader([]byte(body)))
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
request.Header.Add("Content-Type", "application/json")
|
|
|
|
var createdProject struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := ce.httpDo(request, &createdProject); err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
return createdProject.ID, nil
|
|
}
|
|
|
|
func (ce *consoleEndpoints) createAPIKey(ctx context.Context, token, projectID string) (string, error) {
|
|
rng := rand.NewSource(time.Now().UnixNano())
|
|
apiKeyName := fmt.Sprintf("TestKey-%d", rng.Int63())
|
|
|
|
request, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
ce.APIKeys()+"/create/"+projectID,
|
|
bytes.NewReader([]byte(apiKeyName)))
|
|
if err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
request.AddCookie(&http.Cookie{
|
|
Name: ce.cookieName,
|
|
Value: token,
|
|
})
|
|
|
|
request.Header.Add("Content-Type", "application/json")
|
|
|
|
var createdKey struct {
|
|
Key string `json:"key"`
|
|
}
|
|
if err := ce.httpDo(request, &createdKey); err != nil {
|
|
return "", errs.Wrap(err)
|
|
}
|
|
|
|
return createdKey.Key, nil
|
|
}
|
|
|
|
func generateActivationKey(userID uuid.UUID, email string, createdAt time.Time) (string, error) {
|
|
claims := consoleauth.Claims{
|
|
ID: userID,
|
|
Email: email,
|
|
Expiration: createdAt.Add(24 * time.Hour),
|
|
}
|
|
|
|
// TODO: change it in future, when satellite/console secret will be changed
|
|
signer := &consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")}
|
|
|
|
resJSON, err := claims.JSON()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token := consoleauth.Token{Payload: resJSON}
|
|
encoded := base64.URLEncoding.EncodeToString(token.Payload)
|
|
|
|
signature, err := signer.Sign([]byte(encoded))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token.Signature = signature
|
|
|
|
return token.String(), nil
|
|
}
|
|
|
|
func tryReadLine(r io.Reader) string {
|
|
scanner := bufio.NewScanner(r)
|
|
scanner.Scan()
|
|
return scanner.Text()
|
|
}
|