1
- use reqwest:: {
2
- header:: { HeaderMap , HeaderValue } ,
3
- Client , StatusCode ,
4
- } ;
1
+ use reqwest:: { header:: HeaderValue , Client } ;
5
2
use serde:: { Deserialize , Serialize } ;
3
+ use serde_json:: json;
6
4
use sqlx:: { types:: ipnetwork:: IpNetwork , PgConnection } ;
7
5
use uuid:: Uuid ;
8
6
@@ -22,6 +20,13 @@ pub struct GithubClient {
22
20
client_secret : String ,
23
21
}
24
22
23
+ #[ derive( Deserialize ) ]
24
+ pub struct GitHubFetchedUser {
25
+ pub id : i64 ,
26
+ #[ serde( alias = "login" ) ]
27
+ pub username : String ,
28
+ }
29
+
25
30
impl GithubClient {
26
31
pub fn new ( client_id : String , client_secret : String ) -> GithubClient {
27
32
GithubClient {
@@ -35,10 +40,6 @@ impl GithubClient {
35
40
ip : IpNetwork ,
36
41
pool : & mut PgConnection ,
37
42
) -> Result < GithubLoginAttempt , ApiError > {
38
- #[ derive( Serialize ) ]
39
- struct GithubStartAuthBody {
40
- client_id : String ,
41
- }
42
43
let found_request = GithubLoginAttempt :: get_one_by_ip ( ip, & mut * pool) . await ?;
43
44
if let Some ( r) = found_request {
44
45
if r. is_expired ( ) {
@@ -53,50 +54,36 @@ impl GithubClient {
53
54
} ) ;
54
55
}
55
56
}
56
- let mut headers = HeaderMap :: new ( ) ;
57
- headers. insert ( "Accept" , HeaderValue :: from_static ( "application/json" ) ) ;
58
- let client = match Client :: builder ( ) . default_headers ( headers) . build ( ) {
59
- Err ( e) => {
60
- log:: error!( "{}" , e) ;
61
- return Err ( ApiError :: InternalError ) ;
62
- }
63
- Ok ( c) => c,
64
- } ;
65
- let body = GithubStartAuthBody {
66
- client_id : String :: from ( & self . client_id ) ,
67
- } ;
68
- let json = match serde_json:: to_string ( & body) {
69
- Err ( e) => {
70
- log:: error!( "{}" , e) ;
71
- return Err ( ApiError :: InternalError ) ;
72
- }
73
- Ok ( j) => j,
74
- } ;
75
- let result = match client
57
+
58
+ let res = Client :: new ( )
76
59
. post ( "https://github.com/login/device/code" )
60
+ . header ( "Accept" , HeaderValue :: from_static ( "application/json" ) )
77
61
. basic_auth ( & self . client_id , Some ( & self . client_secret ) )
78
- . body ( json)
62
+ . json ( & json ! ( {
63
+ "client_id" : & self . client_id
64
+ } ) )
79
65
. send ( )
80
66
. await
81
- {
82
- Err ( e) => {
83
- log:: error!( "{}" , e) ;
84
- return Err ( ApiError :: InternalError ) ;
85
- }
86
- Ok ( r) => r,
87
- } ;
67
+ . map_err ( |e| {
68
+ log:: error!( "Failed to start OAuth device flow with GitHub: {}" , e) ;
69
+ ApiError :: InternalError
70
+ } ) ?;
88
71
89
- if result. status ( ) != StatusCode :: OK {
90
- log:: error!( "Couldn't connect to GitHub" ) ;
72
+ if !res. status ( ) . is_success ( ) {
73
+ log:: error!(
74
+ "GitHub OAuth device flow start request failed with code {}" ,
75
+ res. status( )
76
+ ) ;
91
77
return Err ( ApiError :: InternalError ) ;
92
78
}
93
- let body = match result. json :: < GithubStartAuth > ( ) . await {
94
- Err ( e) => {
95
- log:: error!( "{}" , e) ;
96
- return Err ( ApiError :: InternalError ) ;
97
- }
98
- Ok ( b) => b,
99
- } ;
79
+
80
+ let body = res. json :: < GithubStartAuth > ( ) . await . map_err ( |e| {
81
+ log:: error!(
82
+ "Failed to parse OAuth device flow response from GitHub: {}" ,
83
+ e
84
+ ) ;
85
+ ApiError :: InternalError
86
+ } ) ?;
100
87
let uuid = GithubLoginAttempt :: create (
101
88
ip,
102
89
body. device_code ,
@@ -117,81 +104,63 @@ impl GithubClient {
117
104
}
118
105
119
106
pub async fn poll_github ( & self , device_code : & str ) -> Result < String , ApiError > {
120
- #[ derive( Serialize , Debug ) ]
121
- struct GithubPollAuthBody {
122
- client_id : String ,
123
- device_code : String ,
124
- grant_type : String ,
125
- }
126
- let body = GithubPollAuthBody {
127
- client_id : String :: from ( & self . client_id ) ,
128
- device_code : String :: from ( device_code) ,
129
- grant_type : String :: from ( "urn:ietf:params:oauth:grant-type:device_code" ) ,
130
- } ;
131
- let json = match serde_json:: to_string ( & body) {
132
- Err ( e) => {
133
- log:: error!( "{}" , e) ;
134
- return Err ( ApiError :: InternalError ) ;
135
- }
136
- Ok ( j) => j,
137
- } ;
138
- let client = Client :: new ( ) ;
139
- let resp = client
107
+ let resp = Client :: new ( )
140
108
. post ( "https://github.com/login/oauth/access_token" )
141
109
. header ( "Accept" , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) )
142
110
. header (
143
111
"Content-Type" ,
144
112
HeaderValue :: from_str ( "application/json" ) . unwrap ( ) ,
145
113
)
146
114
. basic_auth ( & self . client_id , Some ( & self . client_secret ) )
147
- . body ( json)
115
+ . json ( & json ! ( {
116
+ "client_id" : & self . client_id,
117
+ "device_code" : device_code,
118
+ "grant_type" : "urn:ietf:params:oauth:grant-type:device_code"
119
+ } ) )
148
120
. send ( )
149
- . await ;
150
- if resp. is_err ( ) {
151
- log:: info!( "{}" , resp. err( ) . unwrap( ) ) ;
152
- return Err ( ApiError :: InternalError ) ;
153
- }
154
- let resp = resp. unwrap ( ) ;
155
- let body = resp. json :: < serde_json:: Value > ( ) . await . unwrap ( ) ;
156
- match body. get ( "access_token" ) {
157
- None => {
158
- log:: error!( "{:?}" , body) ;
159
- Err ( ApiError :: BadRequest (
160
- "Request not accepted by user" . to_string ( ) ,
161
- ) )
162
- }
163
- Some ( t) => Ok ( String :: from ( t. as_str ( ) . unwrap ( ) ) ) ,
164
- }
121
+ . await
122
+ . map_err ( |e| {
123
+ log:: error!( "Failed to poll GitHub for developer access token: {}" , e) ;
124
+ ApiError :: InternalError
125
+ } ) ?;
126
+
127
+ Ok ( resp
128
+ . json :: < serde_json:: Value > ( )
129
+ . await
130
+ . map_err ( |e| {
131
+ log:: error!( "Failed to decode GitHub response: {}" , e) ;
132
+ ApiError :: InternalError
133
+ } ) ?
134
+ . get ( "access_token" )
135
+ . ok_or ( ApiError :: BadRequest ( "Request not accepted by user" . into ( ) ) ) ?
136
+ . as_str ( )
137
+ . ok_or_else ( || {
138
+ log:: error!( "Invalid access_token received from GitHub" ) ;
139
+ ApiError :: InternalError
140
+ } ) ?
141
+ . to_string ( ) )
165
142
}
166
143
167
- pub async fn get_user ( & self , token : & str ) -> Result < serde_json:: Value , ApiError > {
168
- let client = Client :: new ( ) ;
169
- let resp = match client
144
+ pub async fn get_user ( & self , token : & str ) -> Result < GitHubFetchedUser , ApiError > {
145
+ let resp = Client :: new ( )
170
146
. get ( "https://api.github.com/user" )
171
147
. header ( "Accept" , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) )
172
148
. header ( "User-Agent" , "geode_index" )
173
149
. bearer_auth ( token)
174
150
. send ( )
175
151
. await
176
- {
177
- Err ( e) => {
178
- log:: info!( "{}" , e) ;
179
- return Err ( ApiError :: InternalError ) ;
180
- }
181
- Ok ( r) => r,
182
- } ;
152
+ . map_err ( |e| {
153
+ log:: error!( "Request to https://api.github.com/user failed: {}" , e) ;
154
+ ApiError :: InternalError
155
+ } ) ?;
183
156
184
157
if !resp. status ( ) . is_success ( ) {
185
158
return Err ( ApiError :: InternalError ) ;
186
159
}
187
- let body = match resp. json :: < serde_json:: Value > ( ) . await {
188
- Err ( e) => {
189
- log:: error!( "{}" , e) ;
190
- return Err ( ApiError :: InternalError ) ;
191
- }
192
- Ok ( b) => b,
193
- } ;
194
160
195
- Ok ( body)
161
+ Ok ( resp. json :: < GitHubFetchedUser > ( ) . await . map_err ( |e| {
162
+ log:: error!( "Failed to create GitHubFetchedUser: {}" , e) ;
163
+ ApiError :: InternalError
164
+ } ) ?)
196
165
}
197
166
}
0 commit comments