-
Notifications
You must be signed in to change notification settings - Fork 642
/
Copy pathutil.rs
162 lines (138 loc) · 5.35 KB
/
util.rs
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
use chrono::Utc;
use conduit_cookie::RequestCookies;
use super::prelude::*;
use crate::middleware::log_request;
use crate::models::persistent_session::SessionCookie;
use crate::models::{ApiToken, PersistentSession, User};
use crate::util::errors::{
account_locked, forbidden, internal, AppError, AppResult, InsecurelyGeneratedTokenRevoked,
};
use conduit_cookie::RequestSession;
#[derive(Debug)]
pub struct AuthenticatedUser {
user: User,
token_id: Option<i32>,
}
impl AuthenticatedUser {
pub fn user_id(&self) -> i32 {
self.user.id
}
pub fn api_token_id(&self) -> Option<i32> {
self.token_id
}
pub fn user(self) -> User {
self.user
}
/// Disallows token authenticated users
pub fn forbid_api_token_auth(self) -> AppResult<Self> {
if self.token_id.is_none() {
Ok(self)
} else {
Err(
internal("API Token authentication was explicitly disallowed for this API")
.chain(forbidden()),
)
}
}
}
/// The Origin header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin)
/// is sent with CORS requests and POST requests, and indicates where the request comes from.
/// We don't want to accept authenticated requests that originated from other sites, so this
/// function returns an error if the Origin header doesn't match what we expect "this site" to
/// be: https://crates.io in production, or http://localhost:port/ in development.
fn verify_origin(req: &dyn RequestExt) -> AppResult<()> {
let headers = req.headers();
let allowed_origins = &req.app().config.allowed_origins;
let bad_origin = headers
.get_all(header::ORIGIN)
.iter()
.filter_map(|value| value.to_str().ok())
.find(|value| !allowed_origins.iter().any(|it| it == value));
if let Some(bad_origin) = bad_origin {
let error_message =
format!("only same-origin requests can be authenticated. got {bad_origin:?}");
return Err(internal(&error_message).chain(forbidden()));
}
Ok(())
}
fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
let conn = req.db_write()?;
// TODO(adsnaider): Remove as part of https://github.com/rust-lang/crates.io/issues/2630.
// Log in with the session id.
let session = req.session();
let user_id_from_session = session.get("user_id").and_then(|s| s.parse::<i32>().ok());
if let Some(id) = user_id_from_session {
let user = User::find(&conn, id)
.map_err(|err| err.chain(internal("user_id from cookie not found in database")))?;
return Ok(AuthenticatedUser {
user,
token_id: None,
});
}
// Log in with persistent session token.
if let Some(Ok(session_cookie)) = req
.cookies()
.get(SessionCookie::SESSION_COOKIE_NAME)
.map(|cookie| cookie.value())
.map(|cookie| cookie.parse::<SessionCookie>())
{
if let Some(session) = PersistentSession::find(session_cookie.session_id(), &conn)? {
if session.is_authorized(session_cookie.token()) {
let user = User::find(&conn, session.user_id).map_err(|e| {
e.chain(internal("user_id from session not found in the database"))
})?;
return Ok(AuthenticatedUser {
user,
token_id: None,
});
}
}
}
// Otherwise, look for an `Authorization` header on the request
let maybe_authorization = req
.headers()
.get(header::AUTHORIZATION)
.and_then(|h| h.to_str().ok());
if let Some(header_value) = maybe_authorization {
let token = ApiToken::find_by_api_token(&conn, header_value).map_err(|e| {
if e.is::<InsecurelyGeneratedTokenRevoked>() {
e
} else {
e.chain(internal("invalid token")).chain(forbidden())
}
})?;
let user = User::find(&conn, token.user_id)
.map_err(|err| err.chain(internal("user_id from token not found in database")))?;
return Ok(AuthenticatedUser {
user,
token_id: Some(token.id),
});
}
// Unable to authenticate the user
return Err(internal("no cookie session or auth header found").chain(forbidden()));
}
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
fn authenticate(&mut self) -> AppResult<AuthenticatedUser> {
verify_origin(self)?;
let authenticated_user = authenticate_user(self)?;
if let Some(reason) = &authenticated_user.user.account_lock_reason {
let still_locked = if let Some(until) = authenticated_user.user.account_lock_until {
until > Utc::now().naive_utc()
} else {
true
};
if still_locked {
return Err(account_locked(
reason,
authenticated_user.user.account_lock_until,
));
}
}
log_request::add_custom_metadata("uid", authenticated_user.user_id());
if let Some(id) = authenticated_user.api_token_id() {
log_request::add_custom_metadata("tokenid", id);
}
Ok(authenticated_user)
}
}