diff --git a/auth.go b/auth.go index e66f0bb..ffba97d 100644 --- a/auth.go +++ b/auth.go @@ -1,75 +1,77 @@ package writeas import ( "fmt" "net/http" ) // LogIn authenticates a user with Write.as. // See https://developer.write.as/docs/api/#authenticate-a-user func (c *Client) LogIn(username, pass string) (*AuthUser, error) { u := &AuthUser{} up := struct { Alias string `json:"alias"` Pass string `json:"pass"` }{ Alias: username, Pass: pass, } env, err := c.post("/auth/login", up, u) if err != nil { return nil, err } var ok bool if u, ok = env.Data.(*AuthUser); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } + u.BaseURL = c.BaseUrl() + status := env.Code if status != http.StatusOK { if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else if status == http.StatusUnauthorized { return nil, fmt.Errorf("Incorrect password.") } else if status == http.StatusNotFound { return nil, fmt.Errorf("User does not exist.") } else if status == http.StatusTooManyRequests { return nil, fmt.Errorf("Stop repeatedly trying to log in.") } return nil, fmt.Errorf("Problem authenticating: %d. %v\n", status, err) } c.SetToken(u.AccessToken) return u, nil } // LogOut logs the current user out, making the Client's current access token // invalid. func (c *Client) LogOut() error { env, err := c.delete("/auth/me", nil) if err != nil { return err } status := env.Code if status != http.StatusNoContent { if status == http.StatusNotFound { return fmt.Errorf("Access token is invalid or doesn't exist") } return fmt.Errorf("Unable to log out: %v", env.ErrorMessage) } // Logout successful, so update the Client c.token = "" return nil } func (c *Client) isNotLoggedIn(code int) bool { if c.token == "" { return false } return code == http.StatusUnauthorized } diff --git a/user.go b/user.go index e10f3c8..dc4e6b9 100644 --- a/user.go +++ b/user.go @@ -1,36 +1,37 @@ package writeas import ( "time" ) type ( // AuthUser represents a just-authenticated user. It contains information // that'll only be returned once (now) per user session. AuthUser struct { + BaseURL string `json:"baseUrl,omitempty"` AccessToken string `json:"access_token,omitempty"` Password string `json:"password,omitempty"` User *User `json:"user"` } // User represents a registered Write.as user. User struct { Username string `json:"username"` Email string `json:"email"` Created time.Time `json:"created"` // Optional properties Subscription *UserSubscription `json:"subscription"` } // UserSubscription contains information about a user's Write.as // subscription. UserSubscription struct { Name string `json:"name"` Begin time.Time `json:"begin"` End time.Time `json:"end"` AutoRenew bool `json:"auto_renew"` Active bool `json:"is_active"` Delinquent bool `json:"is_delinquent"` } ) diff --git a/writeas.go b/writeas.go index f31cfbc..714ee6d 100644 --- a/writeas.go +++ b/writeas.go @@ -1,198 +1,211 @@ // Package writeas provides the binding for the Write.as API package writeas import ( "bytes" - "code.as/core/socks" "encoding/json" "fmt" - "github.com/writeas/impart" "io" "net/http" "time" + + "code.as/core/socks" + "github.com/writeas/impart" ) const ( apiURL = "https://write.as/api" devAPIURL = "https://development.write.as/api" torAPIURL = "http://writeas7pm7rcdqg.onion/api" // Current go-writeas version Version = "2-dev" ) // Client is used to interact with the Write.as API. It can be used to make // authenticated or unauthenticated calls. type Client struct { baseURL string // Access token for the user making requests. token string // Client making requests to the API client *http.Client // UserAgent overrides the default User-Agent header UserAgent string } // defaultHTTPTimeout is the default http.Client timeout. const defaultHTTPTimeout = 10 * time.Second // NewClient creates a new API client. By default, all requests are made // unauthenticated. To optionally make authenticated requests, call `SetToken`. // // c := writeas.NewClient() // c.SetToken("00000000-0000-0000-0000-000000000000") func NewClient() *Client { return NewClientWith(Config{URL: apiURL}) } // NewTorClient creates a new API client for communicating with the Write.as // Tor hidden service, using the given port to connect to the local SOCKS // proxy. func NewTorClient(port int) *Client { return NewClientWith(Config{URL: torAPIURL, TorPort: port}) } // NewDevClient creates a new API client for development and testing. It'll // communicate with our development servers, and SHOULD NOT be used in // production. func NewDevClient() *Client { return NewClientWith(Config{URL: devAPIURL}) } // Config configures a Write.as client. type Config struct { // URL of the Write.as API service. Defaults to https://write.as/api. URL string // If specified, the API client will communicate with the Write.as Tor // hidden service using the provided port to connect to the local SOCKS // proxy. TorPort int // If specified, requests will be authenticated using this user token. // This may be provided after making a few anonymous requests with // SetToken. Token string } // NewClientWith builds a new API client with the provided configuration. func NewClientWith(c Config) *Client { if c.URL == "" { c.URL = apiURL } httpClient := &http.Client{Timeout: defaultHTTPTimeout} if c.TorPort > 0 { dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", c.TorPort)) httpClient.Transport = &http.Transport{Dial: dialSocksProxy} } return &Client{ client: httpClient, baseURL: c.URL, token: c.Token, } } // SetToken sets the user token for all future Client requests. Setting this to // an empty string will change back to unauthenticated requests. func (c *Client) SetToken(token string) { c.token = token } // Token returns the user token currently set to the Client. func (c *Client) Token() string { return c.token } +// SetBaseUrl sets the baseUrl for all future Client requests +func (c *Client) SetBaseUrl(baseUrl string) { + if baseUrl != "" { + c.baseURL = baseUrl + } +} + +// BaseUrl returns the set baseUrl +func (c *Client) BaseUrl() string { + return c.baseURL +} + func (c *Client) get(path string, r interface{}) (*impart.Envelope, error) { method := "GET" if method != "GET" && method != "HEAD" { return nil, fmt.Errorf("Method %s not currently supported by library (only HEAD and GET).\n", method) } return c.request(method, path, nil, r) } func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(data) return c.request("POST", path, b, r) } func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(data) return c.request("PUT", path, b, r) } func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) { r, err := c.buildRequest("DELETE", path, nil) if err != nil { return nil, err } q := r.URL.Query() for k, v := range data { q.Add(k, v) } r.URL.RawQuery = q.Encode() return c.doRequest(r, nil) } func (c *Client) request(method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) { r, err := c.buildRequest(method, path, data) if err != nil { return nil, err } return c.doRequest(r, result) } func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) { url := fmt.Sprintf("%s%s", c.baseURL, path) r, err := http.NewRequest(method, url, data) if err != nil { return nil, fmt.Errorf("Create request: %v", err) } c.prepareRequest(r) return r, nil } func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelope, error) { resp, err := c.client.Do(r) if err != nil { return nil, fmt.Errorf("Request: %v", err) } defer resp.Body.Close() env := &impart.Envelope{ Code: resp.StatusCode, } if result != nil { env.Data = result err = json.NewDecoder(resp.Body).Decode(&env) if err != nil { return nil, err } } return env, nil } func (c *Client) prepareRequest(r *http.Request) { ua := c.UserAgent if ua == "" { ua = "go-writeas v" + Version } r.Header.Add("User-Agent", ua) r.Header.Add("Content-Type", "application/json") if c.token != "" { r.Header.Add("Authorization", "Token "+c.token) } }