diff --git a/apps.go b/apps.go index 301fa6f..b03f9b1 100644 --- a/apps.go +++ b/apps.go @@ -18,7 +18,9 @@ type AppConfig struct { // Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob) RedirectURIs string - // This can be a space-separated list of the following items: "read", "write" and "follow". + // This can be a space-separated list of items listed on the /settings/applications/new page of any Mastodon + // instance. "read", "write", and "follow" are top-level scopes that include all the permissions of the more + // specific scopes like "read:favourites", "write:statuses", and "write:follows". Scopes string // Optional. @@ -31,6 +33,9 @@ type Application struct { RedirectURI string `json:"redirect_uri"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` + + // AuthURI is not part of the Mastodon API; it is generated by go-mastodon. + AuthURI string `json:"auth_uri,omitempty"` } // RegisterApp returns the mastodon application. @@ -73,5 +78,19 @@ func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error return nil, err } + u, err = url.Parse(appConfig.Server) + if err != nil { + return nil, err + } + u.Path = path.Join(u.Path, "/oauth/authorize") + u.RawQuery = url.Values{ + "scope": {appConfig.Scopes}, + "response_type": {"code"}, + "redirect_uri": {app.RedirectURI}, + "client_id": {app.ClientID}, + }.Encode() + + app.AuthURI = u.String() + return &app, nil } diff --git a/mastodon.go b/mastodon.go index 68db1ba..a856606 100644 --- a/mastodon.go +++ b/mastodon.go @@ -130,14 +130,34 @@ func NewClient(config *Config) *Client { // Authenticate get access-token to the API. func (c *Client) Authenticate(ctx context.Context, username, password string) error { - params := url.Values{} - params.Set("client_id", c.config.ClientID) - params.Set("client_secret", c.config.ClientSecret) - params.Set("grant_type", "password") - params.Set("username", username) - params.Set("password", password) - params.Set("scope", "read write follow") + params := url.Values{ + "client_id": {c.config.ClientID}, + "client_secret": {c.config.ClientSecret}, + "grant_type": {"password"}, + "username": {username}, + "password": {password}, + "scope": {"read write follow"}, + } + return c.authenticate(ctx, params) +} + +// AuthenticateToken logs in using a grant token returned by Application.AuthURI. +// +// redirectURI should be the same as Application.RedirectURI. +func (c *Client) AuthenticateToken(ctx context.Context, authCode, redirectURI string) error { + params := url.Values{ + "client_id": {c.config.ClientID}, + "client_secret": {c.config.ClientSecret}, + "grant_type": {"authorization_code"}, + "code": {authCode}, + "redirect_uri": {redirectURI}, + } + + return c.authenticate(ctx, params) +} + +func (c *Client) authenticate(ctx context.Context, params url.Values) error { u, err := url.Parse(c.config.Server) if err != nil { return err @@ -160,9 +180,9 @@ func (c *Client) Authenticate(ctx context.Context, username, password string) er return parseAPIError("bad authorization", resp) } - res := struct { + var res struct { AccessToken string `json:"access_token"` - }{} + } err = json.NewDecoder(resp.Body).Decode(&res) if err != nil { return err