1- use reqwest:: {
2- header:: { HeaderMap , HeaderValue } ,
3- Client , StatusCode ,
4- } ;
1+ use reqwest:: { header:: HeaderValue , Client } ;
52use serde:: { Deserialize , Serialize } ;
3+ use serde_json:: json;
64use sqlx:: { types:: ipnetwork:: IpNetwork , PgConnection } ;
75use uuid:: Uuid ;
86
@@ -22,6 +20,13 @@ pub struct GithubClient {
2220 client_secret : String ,
2321}
2422
23+ #[ derive( Deserialize ) ]
24+ pub struct GitHubFetchedUser {
25+ pub id : i64 ,
26+ #[ serde( alias = "login" ) ]
27+ pub username : String ,
28+ }
29+
2530impl GithubClient {
2631 pub fn new ( client_id : String , client_secret : String ) -> GithubClient {
2732 GithubClient {
@@ -35,10 +40,6 @@ impl GithubClient {
3540 ip : IpNetwork ,
3641 pool : & mut PgConnection ,
3742 ) -> Result < GithubLoginAttempt , ApiError > {
38- #[ derive( Serialize ) ]
39- struct GithubStartAuthBody {
40- client_id : String ,
41- }
4243 let found_request = GithubLoginAttempt :: get_one_by_ip ( ip, & mut * pool) . await ?;
4344 if let Some ( r) = found_request {
4445 if r. is_expired ( ) {
@@ -53,50 +54,36 @@ impl GithubClient {
5354 } ) ;
5455 }
5556 }
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 ( )
7659 . post ( "https://github.com/login/device/code" )
60+ . header ( "Accept" , HeaderValue :: from_static ( "application/json" ) )
7761 . basic_auth ( & self . client_id , Some ( & self . client_secret ) )
78- . body ( json)
62+ . json ( & json ! ( {
63+ "client_id" : & self . client_id
64+ } ) )
7965 . send ( )
8066 . 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+ } ) ?;
8871
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+ ) ;
9177 return Err ( ApiError :: InternalError ) ;
9278 }
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+ } ) ?;
10087 let uuid = GithubLoginAttempt :: create (
10188 ip,
10289 body. device_code ,
@@ -117,81 +104,63 @@ impl GithubClient {
117104 }
118105
119106 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 ( )
140108 . post ( "https://github.com/login/oauth/access_token" )
141109 . header ( "Accept" , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) )
142110 . header (
143111 "Content-Type" ,
144112 HeaderValue :: from_str ( "application/json" ) . unwrap ( ) ,
145113 )
146114 . 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+ } ) )
148120 . 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 ( ) )
165142 }
166143
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 ( )
170146 . get ( "https://api.github.com/user" )
171147 . header ( "Accept" , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) )
172148 . header ( "User-Agent" , "geode_index" )
173149 . bearer_auth ( token)
174150 . send ( )
175151 . 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+ } ) ?;
183156
184157 if !resp. status ( ) . is_success ( ) {
185158 return Err ( ApiError :: InternalError ) ;
186159 }
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- } ;
194160
195- Ok ( body)
161+ Ok ( resp. json :: < GitHubFetchedUser > ( ) . await . map_err ( |e| {
162+ log:: error!( "Failed to create GitHubFetchedUser: {}" , e) ;
163+ ApiError :: InternalError
164+ } ) ?)
196165 }
197166}
0 commit comments