diff --git a/cmd/client.go b/cmd/client.go index 4bf4618e460..1987674fa0d 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -18,6 +18,7 @@ type Client struct { Auth Authenticator UserInitializer UserInitializer Runner Runner + RemoteClient RemoteClient } func (cli *Client) errorOut(err error) error { diff --git a/cmd/client_remote.go b/cmd/client_remote.go index d81c1c97e28..24b4c893724 100644 --- a/cmd/client_remote.go +++ b/cmd/client_remote.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/manyminds/api2go/jsonapi" homedir "github.com/mitchellh/go-homedir" + "github.com/smartcontractkit/chainlink/store" "github.com/smartcontractkit/chainlink/store/models" "github.com/smartcontractkit/chainlink/store/presenters" "github.com/smartcontractkit/chainlink/utils" @@ -25,12 +27,7 @@ import ( // DisplayAccountBalance renders a table containing the active account address // with it's ETH & LINK balance func (cli *Client) DisplayAccountBalance(c *clipkg.Context) error { - cfg := cli.Config - resp, err := utils.BasicAuthGet( - cfg.BasicAuthUsername, - cfg.BasicAuthPassword, - cfg.ClientNodeURL+"/v2/account_balance", - ) + resp, err := cli.RemoteClient.Get("/v2/account_balance") if err != nil { return cli.errorOut(err) } @@ -46,10 +43,10 @@ func (cli *Client) DisplayAccountBalance(c *clipkg.Context) error { // ShowJobSpec returns the status of the given JobID. func (cli *Client) ShowJobSpec(c *clipkg.Context) error { - cfg := cli.Config if !c.Args().Present() { return cli.errorOut(errors.New("Must pass the job id to be shown")) } + cfg := cli.Config resp, err := utils.BasicAuthGet( cfg.BasicAuthUsername, cfg.BasicAuthPassword, @@ -348,3 +345,77 @@ func (cli *Client) renderAPIResponse(resp *http.Response, dst interface{}) error } return cli.errorOut(cli.Render(dst)) } + +type RemoteClient interface { + Get(string) (*http.Response, error) + Post(string) (*http.Response, error) +} + +type httpPrompterClient struct { + config store.Config + client *http.Client + prompter Prompter +} + +func NewHttpPrompterClient(cfg store.Config, prompter Prompter) RemoteClient { + return &httpPrompterClient{ + config: cfg, + client: &http.Client{}, + prompter: prompter, + } +} + +func (h *httpPrompterClient) Get(path string) (*http.Response, error) { + cookie, err := h.login() + if err != nil { + return nil, err + } + + request, err := http.NewRequest("GET", h.config.ClientNodeURL+path, nil) + if err != nil { + return nil, err + } + + request.AddCookie(cookie) + return h.client.Do(request) +} + +func (h *httpPrompterClient) Post(path string) (*http.Response, error) { + return nil, errors.New("bogus") +} + +func (h *httpPrompterClient) login() (*http.Cookie, error) { + url := h.config.ClientNodeURL + "/sessions" + email := h.prompter.Prompt("Enter email: ") + pwd := h.prompter.PasswordPrompt("Enter password: ") + sessionRequest := models.SessionRequest{Email: email, Password: pwd} + b := new(bytes.Buffer) + err := json.NewEncoder(b).Encode(sessionRequest) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", url, b) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := h.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + _, err = parseResponse(resp) + if err != nil { + return nil, err + } + + cookies := resp.Cookies() + fmt.Println(cookies) + fmt.Println("response", resp) + if len(cookies) < 1 { + return nil, errors.New("Did not receive cookie with session id") + } + return cookies[0], nil +} diff --git a/main.go b/main.go index ff04b90b959..f173efae689 100644 --- a/main.go +++ b/main.go @@ -140,12 +140,14 @@ func Run(client *cmd.Client, args ...string) { // NewProductionClient configures an instance of the CLI to be used // in production. func NewProductionClient() *cmd.Client { + cfg := store.NewConfig() return &cmd.Client{ Renderer: cmd.RendererTable{Writer: os.Stdout}, - Config: store.NewConfig(), + Config: cfg, AppFactory: cmd.ChainlinkAppFactory{}, Auth: cmd.TerminalAuthenticator{Prompter: cmd.NewTerminalPrompter()}, UserInitializer: cmd.NewTerminalUserInitializer(), Runner: cmd.ChainlinkRunner{}, + RemoteClient: cmd.NewHttpPrompterClient(cfg, cmd.NewTerminalPrompter()), } } diff --git a/store/models/orm.go b/store/models/orm.go index e203fa72622..a3fd65de184 100644 --- a/store/models/orm.go +++ b/store/models/orm.go @@ -1,6 +1,7 @@ package models import ( + "errors" "fmt" "math/big" "reflect" @@ -317,6 +318,19 @@ func (orm *ORM) FindUserBySession(sessionId string) (User, error) { return user, nil } +func (orm *ORM) CheckPasswordForSession(sr SessionRequest) (string, error) { + user, err := orm.FindUser() + if err != nil { + return "", err + } + + if utils.CheckPasswordHash(sr.Password, user.HashedPassword) { + user.SessionID = utils.NewBytes32ID() + return user.SessionID, orm.Save(&user) + } + return "", errors.New("Invalid password") +} + func (orm *ORM) DeleteUser() (User, error) { user, err := orm.FindUser() if err != nil { diff --git a/store/models/user.go b/store/models/user.go index 23f3821821e..7c27c0309da 100644 --- a/store/models/user.go +++ b/store/models/user.go @@ -25,3 +25,8 @@ func NewUser(email, plainPwd string) (User, error) { CreatedAt: Time{Time: time.Now()}, }, nil } + +type SessionRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} diff --git a/web/sessions_controller.go b/web/sessions_controller.go index 497b713ffc0..874475cff4e 100644 --- a/web/sessions_controller.go +++ b/web/sessions_controller.go @@ -3,8 +3,10 @@ package web import ( "errors" + "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" "github.com/smartcontractkit/chainlink/services" + "github.com/smartcontractkit/chainlink/store/models" ) // SnapshotsController manages Snapshot requests. @@ -16,9 +18,22 @@ type SessionsController struct { // Example: // "/assignments/:AID/snapshots" func (sc *SessionsController) Create(c *gin.Context) { - publicError(c, 404, errors.New("Job not found")) + session := sessions.Default(c) + var sr models.SessionRequest + if err := c.ShouldBindJSON(&sr); err != nil { + publicError(c, 400, err) + } else if sid, err := sc.App.GetStore().CheckPasswordForSession(sr); err != nil { + publicError(c, 400, err) // TODO: I never differentiate between the errors + } else if err := saveSessionId(session, sid); err != nil { + c.JSON(200, gin.H{}) + } } func (sc *SessionsController) Destroy(c *gin.Context) { publicError(c, 404, errors.New("Job not found")) } + +func saveSessionId(session sessions.Session, sessionId string) error { + session.Set(sessionIdKey, sessionId) + return session.Save() +}