diff --git a/src/api/API.go b/src/api/API.go index e524158..794a84c 100644 --- a/src/api/API.go +++ b/src/api/API.go @@ -3,16 +3,18 @@ package api import ( "bytes" "encoding/json" + "errors" "io" "io/ioutil" "net/http" ) -func Request(path string, data map[string]string, hash interface{}) (response io.Reader,err error) { +func Request(path string, data map[string]interface{}, hash interface{}) (response io.Reader,err error) { var result io.Reader = nil + var body []byte err = nil client := &http.Client{} - if data != nil { + if data != nil && len(data) > 0 { postString, err := json.Marshal(&data) if err!=nil { return nil, err @@ -23,6 +25,9 @@ func Request(path string, data map[string]string, hash interface{}) (response io } req.Header.Set("Accept","application/json") req.Header.Set("User-Agent","grifpkg/cli") + if hash!=nil { + req.Header.Set("Authorization","Bearer "+hash.(string)) + } res, err := client.Do(req) if err!=nil { return nil, err @@ -30,7 +35,7 @@ func Request(path string, data map[string]string, hash interface{}) (response io defer func(Body io.ReadCloser) { err = Body.Close() }(res.Body) - body, err := ioutil.ReadAll(res.Body) + body, err = ioutil.ReadAll(res.Body) if err!=nil { return nil, err } @@ -42,15 +47,27 @@ func Request(path string, data map[string]string, hash interface{}) (response io } req.Header.Set("Accept","application/json") req.Header.Set("User-Agent","grifpkg/cli") + if hash!=nil { + req.Header.Set("Authorization","Bearer "+hash.(string)) + } res, err := client.Do(req) defer func(Body io.ReadCloser) { err = Body.Close() }(res.Body) - body, err := ioutil.ReadAll(res.Body) + body, err = ioutil.ReadAll(res.Body) if err!=nil { return nil, err } result = bytes.NewReader(body) } + type Error struct { + Error interface{} `json:"error"` + } + var errorInfo Error = Error{} + errorReader := bytes.NewReader(body) + _ = json.NewDecoder(errorReader).Decode(&errorInfo) + if errorInfo.Error!=nil { + return result,errors.New(errorInfo.Error.(string)) + } return result, err } \ No newline at end of file diff --git a/src/api/Log.go b/src/api/Log.go index 8881777..2c7f00f 100644 --- a/src/api/Log.go +++ b/src/api/Log.go @@ -1,6 +1,7 @@ package api import ( + "errors" "fmt" "github.com/AlecAivazis/survey/v2" "github.com/briandowns/spinner" @@ -24,16 +25,16 @@ const ( var currentSpinner *spinner.Spinner = spinner.New(spinner.CharSets[40],0) -func Ask(questions []*survey.Question, answers interface{}){ - LogTool(Question, []Message{}, questions, answers) +func Ask(questions []*survey.Question, answers interface{}) (err error){ + return LogTool(Question, []Message{}, questions, answers) } -func Log(level LogLevel, messages []Message){ - LogTool(level, messages, nil, nil) +func Log(level LogLevel, messages []Message) (err error){ + return LogTool(level, messages, nil, nil) } func LogOne(level LogLevel, message string){ - LogTool(level, []Message{ + _ = LogTool(level, []Message{ // ignore error, logOne usually just logs info/warns, not user input { Value: message, Color: nil, @@ -41,7 +42,7 @@ func LogOne(level LogLevel, message string){ }, nil, nil) } -func LogTool(level LogLevel, messages []Message, questions interface{}, answers interface{}) { +func LogTool(level LogLevel, messages []Message, questions interface{}, answers interface{}) (err error) { if level== Progress { if currentSpinner.Delay==0 { currentSpinner = spinner.New(spinner.CharSets[40], 50*time.Millisecond) @@ -79,21 +80,10 @@ func LogTool(level LogLevel, messages []Message, questions interface{}, answers })) if err != nil { answers = nil - LogTool(Warn, []Message{ - { - Value: "error while expecting user input: "+err.Error(), - Color: nil, - }, - }, nil, nil) - return + return err } } else { - LogTool(Warn, []Message{ - { - Value: "survey started but no questions/answers were provided", - Color: nil, - }, - }, nil, nil) + return errors.New("no questions were provided") } } else { prefix := " ¿ " @@ -109,7 +99,7 @@ func LogTool(level LogLevel, messages []Message, questions interface{}, answers prefix = " ✓ " c = color.New(color.FgHiGreen) } - _, err := fmt.Fprintf(color.Output, " %s", c.SprintFunc()(prefix)) + _, err = fmt.Fprintf(color.Output, " %s", c.SprintFunc()(prefix)) for _, message := range messages { if message.Color==nil { _, err = fmt.Fprintf(color.Output, " %s", message.Value) @@ -118,9 +108,7 @@ func LogTool(level LogLevel, messages []Message, questions interface{}, answers } } fmt.Print("\n") - if err != nil { - return - } } } + return err } \ No newline at end of file diff --git a/src/elements/project/Project.go b/src/elements/project/Project.go index 7a48be0..950d25c 100644 --- a/src/elements/project/Project.go +++ b/src/elements/project/Project.go @@ -96,7 +96,7 @@ func (project Project) InstallAll() (release []Release, err error) { } func (project Project) ParseInstallString(string string, version interface{}) (resourceName string, resourceAuthor interface{}, versionTag interface{}){ - if version == nil || (version != nil && strings.HasPrefix(fmt.Sprintf("%v", version),"^")) { + if version == nil || strings.HasPrefix(fmt.Sprintf("%v", version),"^") { versionTag=nil } else { versionTag=fmt.Sprintf("%v", version) @@ -226,7 +226,7 @@ func createProjectFile(path string) (project Project, err error){ Software string }{} - api.Ask([]*survey.Question{ + err = api.Ask([]*survey.Question{ { Name: "name", Prompt: &survey.Input{ @@ -253,11 +253,14 @@ func createProjectFile(path string) (project Project, err error){ Name: "software", Prompt: &survey.Select{ Message: "Which server software are you using (if you are using a fork, select the closest parent)", - Options: []string{"@spigotmc/spigot","@spigotmc/bungeecord","@papermc/paper","@papermc/waterfall"}, + Options: []string{"@spigotmc/spigot", "@spigotmc/bungeecord", "@papermc/paper", "@papermc/waterfall"}, Default: "@minecraft/java", }, }, - },&answers) + }, &answers) + if err != nil { + return Project{}, err + } project.Name=answers.Name project.Software=answers.Software diff --git a/src/elements/project/Release.go b/src/elements/project/Release.go index b304704..dd48d7b 100644 --- a/src/elements/project/Release.go +++ b/src/elements/project/Release.go @@ -52,7 +52,7 @@ func (release Release) GetResolvedDependencies() (dependencies map[string]Resour func (release Release) ListSuggestions() (suggestions []urlSuggestion.UrlSuggestion, err error){ api.LogOne(api.Progress, "querying url suggestions") - request, err := api.Request("resource/release/suggestion/list/", map[string]string{ + request, err := api.Request("resource/release/suggestion/list/", map[string]interface{}{ "release": release.Id, }, nil) if err!=nil{ @@ -96,7 +96,7 @@ func (release Release) GetDownloadable(suggestionIdFallback interface{}) (downlo Selection string }{} - api.Ask([]*survey.Question{ + err := api.Ask([]*survey.Question{ { Name: "selection", Prompt: &survey.Select{ @@ -105,6 +105,9 @@ func (release Release) GetDownloadable(suggestionIdFallback interface{}) (downlo }, }, }, &answers) + if err != nil { + return DownloadableRelease{}, err + } if strings.HasPrefix(answers.Selection,"suggest another URL"){ return DownloadableRelease{}, errors.New("suggest a download URL here: https://grifpkg.com/suggest/"+release.Parent.(Resource).Id) @@ -146,11 +149,11 @@ func (release Release) GetDownloadable(suggestionIdFallback interface{}) (downlo var request interface{} = nil if release.Parent != nil && release.Parent.(Resource).Paid { - request, err = api.Request("resource/release/download/", map[string]string{ + request, err = api.Request("resource/release/download/", map[string]interface{}{ "release": release.Id, }, session.GetHash()) } else { - request, err = api.Request("resource/release/download/", map[string]string{ + request, err = api.Request("resource/release/download/", map[string]interface{}{ "release": release.Id, }, nil) } diff --git a/src/elements/project/Resource.go b/src/elements/project/Resource.go index d3ca028..a0a3596 100644 --- a/src/elements/project/Resource.go +++ b/src/elements/project/Resource.go @@ -23,7 +23,7 @@ type Resource struct { func QueryResources(name string, author interface{}, service interface{}) ([]Resource, error) { api.LogOne(api.Progress, "querying resources") var err error = nil - var data = make(map[string]string, 0) + var data = make(map[string]interface{}, 0) data["name"]=name if author!=nil { data["author"]=author.(string) @@ -39,7 +39,7 @@ func QueryResources(name string, author interface{}, service interface{}) ([]Res func (resource Resource) GetReleases() (releases []Release, err error){ api.LogOne(api.Progress, "getting releases") - request, err := api.Request("resource/release/list/", map[string]string{ + request, err := api.Request("resource/release/list/", map[string]interface{}{ "resource":resource.Id, }, nil) @@ -53,7 +53,7 @@ func (resource Resource) GetReleases() (releases []Release, err error){ func (resource Resource) GetRelease(version interface{}, id interface{}) (releases Release, err error){ api.LogOne(api.Progress, "getting release") - var data map[string]string = make(map[string]string) + var data map[string]interface{} = make(map[string]interface{}) data["resource"] = resource.Id if version!=nil { data["version"] = version.(string) diff --git a/src/elements/session/LoginRequest.go b/src/elements/session/LoginRequest.go index 0641a0a..12a996d 100644 --- a/src/elements/session/LoginRequest.go +++ b/src/elements/session/LoginRequest.go @@ -16,7 +16,7 @@ type LoginRequest struct { func Start() (loginRequest LoginRequest, err error){ api.LogOne(api.Progress,"starting login request") - request, err := api.Request("login/request/start/", map[string]string{}, nil) + request, err := api.Request("login/request/start/", make(map[string]interface{},0), nil) loginRequest = LoginRequest{} err = json.NewDecoder(request).Decode(&loginRequest) return loginRequest, err @@ -24,7 +24,7 @@ func Start() (loginRequest LoginRequest, err error){ func (loginRequest *LoginRequest) Get() (err error){ api.LogOne(api.Progress,"updating login request") - request, err := api.Request("login/request/get/", map[string]string{ + request, err := api.Request("login/request/get/", map[string]interface{}{ "request": loginRequest.Hash, }, nil) err = json.NewDecoder(request).Decode(&loginRequest) @@ -33,7 +33,7 @@ func (loginRequest *LoginRequest) Get() (err error){ func (loginRequest *LoginRequest) Validate(githubToken string) (err error){ api.LogOne(api.Progress,"validating login request") - request, err := api.Request("login/request/validate/", map[string]string{ + request, err := api.Request("login/request/validate/", map[string]interface{}{ "request": loginRequest.Hash, "token": githubToken, }, nil) diff --git a/src/elements/session/Session.go b/src/elements/session/Session.go index 717e6df..bb590d5 100644 --- a/src/elements/session/Session.go +++ b/src/elements/session/Session.go @@ -2,10 +2,12 @@ package session import ( "encoding/json" + "github.com/AlecAivazis/survey/v2" "github.com/fatih/color" "github.com/grifpkg/cli/api" "github.com/zalando/go-keyring" "log" + "strings" ) type Session struct { @@ -18,10 +20,111 @@ type Session struct { Country interface{} `json:"country"` } +func (session *Session) Link() (err error){ + + answers := struct { + Service string + }{} + + var services []string + services = append(services, "spigotmc") + err = api.Ask([]*survey.Question{ + { + Name: "service", + Prompt: &survey.Select{ + Message: "please, select the service you'd like to link to your grifpkg account", + Options: services, + }, + }, + }, &answers) + if err != nil { + return err + } + + // not tried, 0: no error, 1: invalid password, 2: invalid tfa, 3: not tested + var username string + var password string + var tfa interface{} = nil + for errorCode := 3; errorCode !=0; { + + if errorCode!=2 { // password is not right yet + + usernameAndPassword := struct { + Username string + Password string + }{} + + err := api.Ask([]*survey.Question{ + { + Name: "username", + Prompt: &survey.Input{ + Message: "Please, enter your " + answers.Service + " username", + }, + }, + { + Name: "password", + Prompt: &survey.Password{ + Message: "Please, enter your " + answers.Service + " password", + }, + }, + }, &usernameAndPassword) + if err != nil { + return err + } + + username=usernameAndPassword.Username + password=usernameAndPassword.Password + tfa=nil + } else { // password is right, tfa isn't + + tfaCode := struct { + Tfa string + }{} + + err := api.Ask([]*survey.Question{ + { + Name: "tfa", + Prompt: &survey.Password{ + Message: "Please, enter your tfa code for " + answers.Service, + }, + }, + }, &tfaCode) + if err != nil { + return err + } + tfa=tfaCode.Tfa + } + + var params map[string]interface{} = map[string]interface{}{} + params["service"]=answers.Service + params["username"]=username + params["password"]=password + params["tfa"]=tfa + + api.LogOne(api.Progress,"exchanging credentials with "+answers.Service) + _, err := api.Request("accounts/link/", params, session.Hash) + if err!=nil { + if strings.Contains(err.Error(), "two-factor") { + api.LogOne(api.Info,"your username and password were correct, please, enter your two-factor authentication code") + errorCode=2 + } else { + api.LogOne(api.Warn,err.Error()) + errorCode=1 + } + } else { + api.LogOne(api.Progress,"valid credentials exchanged with "+answers.Service) + errorCode=0 + } + + } + + return nil +} + func (session *Session) Close() (err error){ if len(session.Hash)>0 { api.LogOne(api.Progress,"logging out") - request, err := api.Request("session/close/", map[string]string{}, GetHash()) + request, err := api.Request("session/close/", map[string]interface{}{}, GetHash()) if err!=nil { err = nil // ignore error } else { @@ -52,7 +155,7 @@ func Get() (session Session, err error){ if err != nil { api.LogOne(api.Progress,"no sessions found, requesting login") login, err := Start() - api.Log(api.Info, []api.Message{ + _ = api.Log(api.Info, []api.Message{ { Value: "please, login to your grifpkg account by using this link:", Color: nil, diff --git a/src/elements/urlSuggestion/UrlSuggestion.go b/src/elements/urlSuggestion/UrlSuggestion.go index b6dad22..d0d2797 100644 --- a/src/elements/urlSuggestion/UrlSuggestion.go +++ b/src/elements/urlSuggestion/UrlSuggestion.go @@ -20,7 +20,7 @@ type UrlSuggestion struct { func (suggestion UrlSuggestion) Use() error{ api.LogOne(api.Progress, "registering url suggestion use") - _, err := api.Request("resource/release/suggestion/use/", map[string]string{ + _, err := api.Request("resource/release/suggestion/use/", map[string]interface{}{ "suggestion": suggestion.Id, }, nil) return err diff --git a/src/grif.go b/src/grif.go index c24b0dc..0539006 100644 --- a/src/grif.go +++ b/src/grif.go @@ -5,6 +5,7 @@ import ( "github.com/getsentry/sentry-go" "github.com/grifpkg/cli/api" "github.com/grifpkg/cli/elements/project" + "github.com/grifpkg/cli/elements/session" "github.com/grifpkg/cli/installer" "github.com/spf13/cobra" "os" @@ -20,6 +21,25 @@ var rootCMD = &cobra.Command{ }, } +var linkCMD = &cobra.Command{ + Use: "link", + Aliases: []string{"li"}, + Short: "links an external service to your grifpkg account", + Run: func(cmd *cobra.Command, args []string) { + session, err := session.Get() + if err != nil { + api.LogOne(api.Warn, err.Error()) + return + } + err = session.Link() + if err != nil { + api.LogOne(api.Warn, err.Error()) + return + } + api.LogOne(api.Success,"successfully linked account") + }, +} + var importCMD = &cobra.Command{ Use: "import", Aliases: []string{"im"}, @@ -140,4 +160,5 @@ func init() { rootCMD.AddCommand(updateCMD) rootCMD.AddCommand(importCMD) rootCMD.AddCommand(excludeCMD) + rootCMD.AddCommand(linkCMD) } \ No newline at end of file