1/* 2Copyright 2017-2018 Mikael Berthe 3 4Licensed under the MIT license. Please see the LICENSE file is this directory. 5*/ 6 7package madon 8 9import ( 10 "encoding/json" 11 "strings" 12 13 "golang.org/x/net/context" 14 "golang.org/x/oauth2" 15 16 "github.com/pkg/errors" 17 "github.com/sendgrid/rest" 18) 19 20const oAuthRelPath = "/oauth/" 21 22// UserToken represents a user token as returned by the Mastodon API 23type UserToken struct { 24 AccessToken string `json:"access_token"` 25 CreatedAt int64 `json:"created_at"` 26 Scope string `json:"scope"` 27 TokenType string `json:"token_type"` 28} 29 30// LoginBasic does basic user authentication 31func (mc *Client) LoginBasic(username, password string, scopes []string) error { 32 if mc == nil { 33 return ErrUninitializedClient 34 } 35 36 if username == "" { 37 return errors.New("missing username") 38 } 39 if password == "" { 40 return errors.New("missing password") 41 } 42 43 hdrs := make(map[string]string) 44 opts := make(map[string]string) 45 46 hdrs["User-Agent"] = "madon/" + MadonVersion 47 48 opts["grant_type"] = "password" 49 opts["client_id"] = mc.ID 50 opts["client_secret"] = mc.Secret 51 opts["username"] = username 52 opts["password"] = password 53 if len(scopes) > 0 { 54 opts["scope"] = strings.Join(scopes, " ") 55 } 56 57 req := rest.Request{ 58 BaseURL: mc.InstanceURL + oAuthRelPath + "token", 59 Headers: hdrs, 60 QueryParams: opts, 61 Method: rest.Post, 62 } 63 64 r, err := restAPI(req) 65 if err != nil { 66 return err 67 } 68 69 var resp UserToken 70 71 err = json.Unmarshal([]byte(r.Body), &resp) 72 if err != nil { 73 return errors.Wrap(err, "cannot unmarshal server response") 74 } 75 76 mc.UserToken = &resp 77 return nil 78} 79 80// SetUserToken sets an existing user credentials 81// No verification of the arguments is made. 82func (mc *Client) SetUserToken(token, username, password string, scopes []string) error { 83 if mc == nil { 84 return ErrUninitializedClient 85 } 86 87 mc.UserToken = &UserToken{ 88 AccessToken: token, 89 Scope: strings.Join(scopes, " "), 90 TokenType: "bearer", 91 } 92 return nil 93} 94 95// LoginOAuth2 handles OAuth2 authentication 96// If code is empty, the URL to the server consent page will be returned; 97// if not, the user token is set. 98func (mc *Client) LoginOAuth2(code string, scopes []string) (string, error) { 99 if mc == nil { 100 return "", ErrUninitializedClient 101 } 102 103 conf := &oauth2.Config{ 104 ClientID: mc.ID, 105 ClientSecret: mc.Secret, 106 Scopes: scopes, 107 Endpoint: oauth2.Endpoint{ 108 AuthURL: mc.InstanceURL + oAuthRelPath + "authorize", 109 TokenURL: mc.InstanceURL + oAuthRelPath + "token", 110 }, 111 RedirectURL: NoRedirect, 112 } 113 114 if code == "" { 115 // URL to consent page to ask for permission 116 // for the scopes specified above. 117 return conf.AuthCodeURL("state", oauth2.AccessTypeOffline), nil 118 } 119 120 // Return token 121 t, err := conf.Exchange(context.TODO(), code) 122 if err != nil { 123 return "", errors.Wrap(err, "cannot convert code into a token") 124 } 125 if t == nil || t.AccessToken == "" { 126 return "", errors.New("empty token") 127 } 128 return "", mc.SetUserToken(t.AccessToken, "", "", scopes) 129} 130