1
1
use crate :: { domain:: SubscriberEmail , email_client:: EmailClient , routes:: error_chain_fmt} ;
2
- use actix_web:: http:: StatusCode ;
3
- use actix_web:: { web, HttpResponse , ResponseError } ;
2
+ use actix_web:: http:: header:: { HeaderMap , HeaderValue } ;
3
+ use actix_web:: http:: { header, StatusCode } ;
4
+ use actix_web:: { web, HttpRequest , HttpResponse , ResponseError } ;
4
5
use anyhow:: Context ;
6
+ use secrecy:: { ExposeSecret , Secret } ;
5
7
use sqlx:: PgPool ;
6
8
7
9
#[ derive( serde:: Deserialize ) ]
@@ -39,14 +41,26 @@ pub struct Content {
39
41
40
42
#[ derive( thiserror:: Error ) ]
41
43
pub enum PublishError {
44
+ #[ error( "Authentication failed" ) ]
45
+ AuthError ( #[ source] anyhow:: Error ) ,
42
46
#[ error( transparent) ]
43
47
UnexpectedError ( #[ from] anyhow:: Error ) ,
44
48
}
45
49
46
50
impl ResponseError for PublishError {
47
- fn status_code ( & self ) -> StatusCode {
51
+ fn error_response ( & self ) -> HttpResponse {
48
52
match self {
49
- PublishError :: UnexpectedError ( _) => StatusCode :: INTERNAL_SERVER_ERROR ,
53
+ PublishError :: UnexpectedError ( _) => {
54
+ HttpResponse :: new ( StatusCode :: INTERNAL_SERVER_ERROR )
55
+ }
56
+ PublishError :: AuthError ( _) => {
57
+ let mut response = HttpResponse :: new ( StatusCode :: UNAUTHORIZED ) ;
58
+ let header_value = HeaderValue :: from_str ( r#"Basic realm="publish""# ) . unwrap ( ) ;
59
+ response
60
+ . headers_mut ( )
61
+ . insert ( header:: WWW_AUTHENTICATE , header_value) ;
62
+ response
63
+ }
50
64
}
51
65
}
52
66
}
@@ -61,7 +75,9 @@ pub async fn publish_newsletter(
61
75
body : web:: Json < BodyData > ,
62
76
pool : web:: Data < PgPool > ,
63
77
email_client : web:: Data < EmailClient > ,
78
+ request : HttpRequest ,
64
79
) -> Result < HttpResponse , PublishError > {
80
+ let credentials = basic_authentication ( request. headers ( ) ) . map_err ( PublishError :: AuthError ) ?;
65
81
let subscribers = get_confirmed_subscribers ( & pool) . await ?;
66
82
for subscriber in subscribers {
67
83
match subscriber {
@@ -86,3 +102,38 @@ pub async fn publish_newsletter(
86
102
}
87
103
Ok ( HttpResponse :: Ok ( ) . finish ( ) )
88
104
}
105
+
106
+ struct Credentials {
107
+ username : String ,
108
+ password : Secret < String > ,
109
+ }
110
+
111
+ fn basic_authentication ( headers : & HeaderMap ) -> Result < Credentials , anyhow:: Error > {
112
+ let header_value = headers
113
+ . get ( "Authorization" )
114
+ . context ( "Missing authorization header" ) ?
115
+ . to_str ( )
116
+ . context ( "The authorization header value is invalid UTF-8" ) ?;
117
+
118
+ let base64_encoded_segment = header_value
119
+ . strip_prefix ( "Basic " )
120
+ . context ( "The Authorization scheme was not 'Basic'. " ) ?;
121
+ let decoded_bytes = base64:: decode_config ( base64_encoded_segment, base64:: STANDARD )
122
+ . context ( "Failed to base64 decode 'Basic' credentials." ) ?;
123
+ let decoded_credentials =
124
+ String :: from_utf8 ( decoded_bytes) . context ( "The decoded credentials are not valid UTF-8." ) ?;
125
+
126
+ let mut credentials = decoded_credentials. splitn ( 2 , ':' ) ;
127
+ let username = credentials
128
+ . next ( )
129
+ . ok_or_else ( || anyhow:: anyhow!( "The username is missing." ) ) ?
130
+ . to_owned ( ) ;
131
+ let password = credentials
132
+ . next ( )
133
+ . ok_or_else ( || anyhow:: anyhow!( "The password is missing. Username: {}" , username) ) ?;
134
+
135
+ Ok ( Credentials {
136
+ username,
137
+ password : Secret :: new ( password. to_owned ( ) ) ,
138
+ } )
139
+ }
0 commit comments