1 /* |
|
2 Copyright 2017-2018 Mikael Berthe |
|
3 |
|
4 Licensed under the MIT license. Please see the LICENSE file is this directory. |
|
5 */ |
|
6 |
|
7 package madon |
|
8 |
|
9 import ( |
|
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 |
|
20 const oAuthRelPath = "/oauth/" |
|
21 |
|
22 // UserToken represents a user token as returned by the Mastodon API |
|
23 type 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 |
|
31 func (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. |
|
82 func (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. |
|
98 func (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 } |
|