1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
use crate::middleware::parse_issuer_from_request;
use actix_web::{error::ParseError, web, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use awc::http::header::Header;
use jsonwebtoken::errors::ErrorKind;
use myc_config::optional_config::OptionalConfig;
use myc_core::models::AccountLifeCycle;
use myc_http_tools::{
functions::decode_jwt_hs512,
models::{
auth_config::AuthConfig,
external_providers_config::ExternalProviderConfig,
internal_auth_config::InternalOauthConfig,
},
responses::GatewayError,
settings::MYCELIUM_PROVIDER_KEY,
Email,
};
#[tracing::instrument(name = "get_email_or_provider_from_request", skip_all)]
pub(super) async fn get_email_or_provider_from_request(
req: HttpRequest,
) -> Result<(Option<Email>, Option<ExternalProviderConfig>, String), GatewayError>
{
// ? -----------------------------------------------------------------------
// ? Extract auth config from request
//
// Auth config must be available in the request after injected on the API
// initialization. If not, returns an InternalServerError.
//
// ? -----------------------------------------------------------------------
let req_auth_config = if let Some(config) =
req.app_data::<web::Data<AuthConfig>>()
{
config
} else {
tracing::error!(
"Unable to extract AuthConfig from request. Authentication will not be completed"
);
return Err(GatewayError::InternalServerError(
"Unable to initialize auth config".to_string(),
));
};
// ? -----------------------------------------------------------------------
// ? Extract issuer from request
//
// Issuer should be used to start the token validation process.
//
// ? -----------------------------------------------------------------------
let (issuer, token) = parse_issuer_from_request(req.clone()).await?;
tracing::trace!("Issuer: {}", issuer);
// ? -----------------------------------------------------------------------
// ? Try to fetch email using internal provider
//
// The internal provider is used to fetch the email from the request. If
// the email is found, returns a tuple with the email, None and the auth.
//
// ? -----------------------------------------------------------------------
if issuer.to_lowercase() == MYCELIUM_PROVIDER_KEY.to_string().to_lowercase()
{
if let Some(email) =
extract_email_from_internal_provider(req.clone()).await?
{
return Ok((Some(email), None, token));
} else {
return Err(GatewayError::Unauthorized(
"Invalid issuer".to_string(),
));
}
}
// ? -----------------------------------------------------------------------
// ? Try to fetch email from external providers
//
// The external providers are used to fetch the email from the request.
// If the email is found, returns a tuple with the email, None and the auth.
//
// ? -----------------------------------------------------------------------
let external_providers = if let OptionalConfig::Enabled(config) =
&req_auth_config.external
{
config
} else {
tracing::error!(
"Unable to extract external providers from request. Authentication will not be completed"
);
return Err(GatewayError::Unauthorized(
"Authentication with external providers disabled".to_string(),
));
};
for provider in external_providers {
let local_issuer =
provider.issuer.async_get_or_error().await.map_err(|_| {
GatewayError::Unauthorized(
"Could not check issuer.".to_string(),
)
})?;
if local_issuer.to_lowercase() == issuer.to_lowercase() {
return Ok((None, Some(provider.to_owned()), token));
}
}
// ? -----------------------------------------------------------------------
// ? Return error
//
// If the issuer is not valid, returns an Unauthorized error.
//
// ? -----------------------------------------------------------------------
tracing::error!("Invalid issuer: {}", issuer);
Err(GatewayError::Unauthorized(
"Token issuer not found".to_string(),
))
}
/// Try to fetch email from internal provider
///
/// This function is used to fetch the email from the request using the internal
/// provider. If the email is found, returns a tuple with the email, None and the
/// auth.
///
#[tracing::instrument(name = "try_to_fetch_from_internal_provider", skip_all)]
async fn extract_email_from_internal_provider(
req: HttpRequest,
) -> Result<Option<Email>, GatewayError> {
tracing::trace!("Checking credentials with Mycelium Auth");
//
// Extract the internal OAuth2 configuration from the HTTP request. If
// the configuration is not available returns a None.
//
let req_auth_config = match req.app_data::<web::Data<InternalOauthConfig>>()
{
Some(config) => config.jwt_secret.to_owned(),
None => return Err(GatewayError::InternalServerError(
"Unexpected error on validate internal auth config. Please contact the system administrator.".to_string(),
)),
};
//
// Extract the token from the request. If the token is not available
// returns a InternalServerError response.
//
let jwt_token = match req_auth_config.async_get_or_error().await {
Ok(token) => token,
Err(err) => {
return Err(GatewayError::InternalServerError(format!(
"Unexpected error on get jwt token: {err}"
)));
}
};
//
// Resolve the expected audience from AccountLifeCycle (domain_url or
// domain_name fallback) — must match what encode_jwt wrote into aud.
//
let core_config = match req.app_data::<web::Data<AccountLifeCycle>>() {
Some(c) => c.get_ref().to_owned(),
None => {
return Err(GatewayError::InternalServerError(
"Core config not available in request.".to_string(),
))
}
};
let audience =
match core_config.domain_url {
Some(url) => url.async_get_or_error().await.map_err(|e| {
GatewayError::InternalServerError(format!(
"Could not resolve domain_url for audience: {e}"
))
})?,
None => {
core_config.domain_name.async_get_or_error().await.map_err(
|e| {
GatewayError::InternalServerError(format!(
"Could not resolve domain_name for audience: {e}"
))
},
)?
}
};
//
// Extract the bearer from the request. If the bearer is not available
// returns a Unauthorized response.
//
let token = match Authorization::<Bearer>::parse(&req) {
Err(err) => match err {
ParseError::Header => {
return Err(GatewayError::Unauthorized(format!(
"Bearer token not found or invalid in request: {err}"
)));
}
_ => {
return Err(GatewayError::Unauthorized(format!(
"Invalid Bearer token: {err}"
)));
}
},
Ok(res) => res,
};
//
// Decode the JWT token. If the token is not valid returns a
// Unauthorized response.
//
match decode_jwt_hs512(token, jwt_token, &audience) {
Err(err) => match err.kind() {
ErrorKind::ExpiredSignature => {
return Err(GatewayError::Unauthorized(format!(
"Expired token: {err}"
)));
}
_ => {
return Err(GatewayError::Unauthorized(format!(
"Unexpected error on decode jwt token: {err}"
)))
}
},
Ok(res) => {
let claims = res.claims;
let email = claims.email;
match Email::from_string(email) {
Err(err) => {
return Err(GatewayError::Unauthorized(format!(
"Invalid email: {err}"
)));
}
Ok(res) => return Ok(Some(res)),
}
}
}
}