Skip to content

Commit 0895979

Browse files
authored
Extract auth module (#5530)
1 parent 066d38c commit 0895979

File tree

5 files changed

+127
-122
lines changed

5 files changed

+127
-122
lines changed

Diff for: src/auth.rs

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use crate::controllers;
2+
use crate::db::RequestTransaction;
3+
use crate::middleware::log_request;
4+
use crate::models::{ApiToken, User};
5+
use crate::util::errors::{
6+
account_locked, forbidden, internal, AppError, AppResult, InsecurelyGeneratedTokenRevoked,
7+
};
8+
use chrono::Utc;
9+
use conduit::RequestExt;
10+
use conduit_cookie::RequestSession;
11+
use http::header;
12+
13+
#[derive(Debug)]
14+
pub struct AuthenticatedUser {
15+
user: User,
16+
token_id: Option<i32>,
17+
}
18+
19+
impl AuthenticatedUser {
20+
pub fn user_id(&self) -> i32 {
21+
self.user.id
22+
}
23+
24+
pub fn api_token_id(&self) -> Option<i32> {
25+
self.token_id
26+
}
27+
28+
pub fn user(self) -> User {
29+
self.user
30+
}
31+
32+
/// Disallows token authenticated users
33+
pub fn forbid_api_token_auth(self) -> AppResult<Self> {
34+
if self.token_id.is_none() {
35+
Ok(self)
36+
} else {
37+
Err(
38+
internal("API Token authentication was explicitly disallowed for this API")
39+
.chain(forbidden()),
40+
)
41+
}
42+
}
43+
}
44+
45+
fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
46+
let conn = req.db_write()?;
47+
48+
let session = req.session();
49+
let user_id_from_session = session.get("user_id").and_then(|s| s.parse::<i32>().ok());
50+
51+
if let Some(id) = user_id_from_session {
52+
let user = User::find(&conn, id)
53+
.map_err(|err| err.chain(internal("user_id from cookie not found in database")))?;
54+
55+
return Ok(AuthenticatedUser {
56+
user,
57+
token_id: None,
58+
});
59+
}
60+
61+
// Otherwise, look for an `Authorization` header on the request
62+
let maybe_authorization = req
63+
.headers()
64+
.get(header::AUTHORIZATION)
65+
.and_then(|h| h.to_str().ok());
66+
67+
if let Some(header_value) = maybe_authorization {
68+
let token = ApiToken::find_by_api_token(&conn, header_value).map_err(|e| {
69+
if e.is::<InsecurelyGeneratedTokenRevoked>() {
70+
e
71+
} else {
72+
e.chain(internal("invalid token")).chain(forbidden())
73+
}
74+
})?;
75+
76+
let user = User::find(&conn, token.user_id)
77+
.map_err(|err| err.chain(internal("user_id from token not found in database")))?;
78+
79+
return Ok(AuthenticatedUser {
80+
user,
81+
token_id: Some(token.id),
82+
});
83+
}
84+
85+
// Unable to authenticate the user
86+
return Err(internal("no cookie session or auth header found").chain(forbidden()));
87+
}
88+
89+
pub trait UserAuthenticationExt {
90+
fn authenticate(&mut self) -> AppResult<AuthenticatedUser>;
91+
}
92+
93+
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
94+
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
95+
fn authenticate(&mut self) -> AppResult<AuthenticatedUser> {
96+
controllers::util::verify_origin(self)?;
97+
98+
let authenticated_user = authenticate_user(self)?;
99+
100+
if let Some(reason) = &authenticated_user.user.account_lock_reason {
101+
let still_locked = if let Some(until) = authenticated_user.user.account_lock_until {
102+
until > Utc::now().naive_utc()
103+
} else {
104+
true
105+
};
106+
if still_locked {
107+
return Err(account_locked(
108+
reason,
109+
authenticated_user.user.account_lock_until,
110+
));
111+
}
112+
}
113+
114+
log_request::add_custom_metadata("uid", authenticated_user.user_id());
115+
if let Some(id) = authenticated_user.api_token_id() {
116+
log_request::add_custom_metadata("tokenid", id);
117+
}
118+
119+
Ok(authenticated_user)
120+
}
121+
}

Diff for: src/controllers.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod prelude {
1515
pub use conduit::{header, RequestExt, StatusCode};
1616
pub use conduit_router::RequestParams;
1717

18+
pub use crate::auth::UserAuthenticationExt;
1819
pub use crate::db::RequestTransaction;
1920
pub use crate::middleware::app::RequestApp;
2021
pub use crate::util::errors::{cargo_err, AppError, AppResult}; // TODO: Remove cargo_err from here
@@ -23,10 +24,6 @@ mod prelude {
2324
use indexmap::IndexMap;
2425
use serde::Serialize;
2526

26-
pub trait UserAuthenticationExt {
27-
fn authenticate(&mut self) -> AppResult<super::util::AuthenticatedUser>;
28-
}
29-
3027
pub trait RequestUtils {
3128
fn redirect(&self, url: String) -> AppResponse;
3229

@@ -74,7 +71,7 @@ mod prelude {
7471
}
7572

7673
pub mod helpers;
77-
mod util;
74+
pub mod util;
7875

7976
pub mod category;
8077
pub mod crate_owner_invitation;

Diff for: src/controllers/crate_owner_invitation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::frontend_prelude::*;
22

3+
use crate::auth::AuthenticatedUser;
34
use crate::controllers::helpers::pagination::{Page, PaginationOptions};
4-
use crate::controllers::util::AuthenticatedUser;
55
use crate::models::{Crate, CrateOwnerInvitation, Rights, User};
66
use crate::schema::{crate_owner_invitations, crates, users};
77
use crate::util::errors::{forbidden, internal};

Diff for: src/controllers/util.rs

+2-116
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,12 @@
1-
use chrono::Utc;
2-
use conduit_cookie::RequestSession;
3-
41
use super::prelude::*;
5-
6-
use crate::middleware::log_request;
7-
use crate::models::{ApiToken, User};
8-
use crate::util::errors::{
9-
account_locked, forbidden, internal, AppError, AppResult, InsecurelyGeneratedTokenRevoked,
10-
};
11-
12-
#[derive(Debug)]
13-
pub struct AuthenticatedUser {
14-
user: User,
15-
token_id: Option<i32>,
16-
}
17-
18-
impl AuthenticatedUser {
19-
pub fn user_id(&self) -> i32 {
20-
self.user.id
21-
}
22-
23-
pub fn api_token_id(&self) -> Option<i32> {
24-
self.token_id
25-
}
26-
27-
pub fn user(self) -> User {
28-
self.user
29-
}
30-
31-
/// Disallows token authenticated users
32-
pub fn forbid_api_token_auth(self) -> AppResult<Self> {
33-
if self.token_id.is_none() {
34-
Ok(self)
35-
} else {
36-
Err(
37-
internal("API Token authentication was explicitly disallowed for this API")
38-
.chain(forbidden()),
39-
)
40-
}
41-
}
42-
}
2+
use crate::util::errors::{forbidden, internal, AppError, AppResult};
433

444
/// The Origin header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin)
455
/// is sent with CORS requests and POST requests, and indicates where the request comes from.
466
/// We don't want to accept authenticated requests that originated from other sites, so this
477
/// function returns an error if the Origin header doesn't match what we expect "this site" to
488
/// be: https://crates.io in production, or http://localhost:port/ in development.
49-
fn verify_origin(req: &dyn RequestExt) -> AppResult<()> {
9+
pub fn verify_origin(req: &dyn RequestExt) -> AppResult<()> {
5010
let headers = req.headers();
5111
let allowed_origins = &req.app().config.allowed_origins;
5212

@@ -63,77 +23,3 @@ fn verify_origin(req: &dyn RequestExt) -> AppResult<()> {
6323
}
6424
Ok(())
6525
}
66-
67-
fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
68-
let conn = req.db_write()?;
69-
70-
let session = req.session();
71-
let user_id_from_session = session.get("user_id").and_then(|s| s.parse::<i32>().ok());
72-
73-
if let Some(id) = user_id_from_session {
74-
let user = User::find(&conn, id)
75-
.map_err(|err| err.chain(internal("user_id from cookie not found in database")))?;
76-
77-
return Ok(AuthenticatedUser {
78-
user,
79-
token_id: None,
80-
});
81-
}
82-
83-
// Otherwise, look for an `Authorization` header on the request
84-
let maybe_authorization = req
85-
.headers()
86-
.get(header::AUTHORIZATION)
87-
.and_then(|h| h.to_str().ok());
88-
89-
if let Some(header_value) = maybe_authorization {
90-
let token = ApiToken::find_by_api_token(&conn, header_value).map_err(|e| {
91-
if e.is::<InsecurelyGeneratedTokenRevoked>() {
92-
e
93-
} else {
94-
e.chain(internal("invalid token")).chain(forbidden())
95-
}
96-
})?;
97-
98-
let user = User::find(&conn, token.user_id)
99-
.map_err(|err| err.chain(internal("user_id from token not found in database")))?;
100-
101-
return Ok(AuthenticatedUser {
102-
user,
103-
token_id: Some(token.id),
104-
});
105-
}
106-
107-
// Unable to authenticate the user
108-
return Err(internal("no cookie session or auth header found").chain(forbidden()));
109-
}
110-
111-
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
112-
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
113-
fn authenticate(&mut self) -> AppResult<AuthenticatedUser> {
114-
verify_origin(self)?;
115-
116-
let authenticated_user = authenticate_user(self)?;
117-
118-
if let Some(reason) = &authenticated_user.user.account_lock_reason {
119-
let still_locked = if let Some(until) = authenticated_user.user.account_lock_until {
120-
until > Utc::now().naive_utc()
121-
} else {
122-
true
123-
};
124-
if still_locked {
125-
return Err(account_locked(
126-
reason,
127-
authenticated_user.user.account_lock_until,
128-
));
129-
}
130-
}
131-
132-
log_request::add_custom_metadata("uid", authenticated_user.user_id());
133-
if let Some(id) = authenticated_user.api_token_id() {
134-
log_request::add_custom_metadata("tokenid", id);
135-
}
136-
137-
Ok(authenticated_user)
138-
}
139-
}

Diff for: src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub mod uploaders;
5454
pub mod util;
5555
pub mod worker;
5656

57+
mod auth;
5758
pub mod controllers;
5859
pub mod models;
5960
mod router;

0 commit comments

Comments
 (0)