Skip to content

Commit 43d317b

Browse files
authored
Merge pull request #5538 from Turbo87/auth-check
auth: Implement AuthCheck struct
2 parents 32e1a4f + 79e30af commit 43d317b

File tree

11 files changed

+93
-80
lines changed

11 files changed

+93
-80
lines changed

src/auth.rs

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,52 @@ use conduit::RequestExt;
1010
use conduit_cookie::RequestSession;
1111
use http::header;
1212

13+
#[derive(Debug, Clone)]
14+
pub struct AuthCheck {
15+
allow_token: bool,
16+
}
17+
18+
impl AuthCheck {
19+
#[must_use]
20+
pub fn default() -> Self {
21+
Self { allow_token: true }
22+
}
23+
24+
#[must_use]
25+
pub fn only_cookie() -> Self {
26+
Self { allow_token: false }
27+
}
28+
29+
pub fn check(&self, request: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
30+
controllers::util::verify_origin(request)?;
31+
32+
let auth = authenticate_user(request)?;
33+
34+
if let Some(reason) = &auth.user.account_lock_reason {
35+
let still_locked = if let Some(until) = auth.user.account_lock_until {
36+
until > Utc::now().naive_utc()
37+
} else {
38+
true
39+
};
40+
if still_locked {
41+
return Err(account_locked(reason, auth.user.account_lock_until));
42+
}
43+
}
44+
45+
log_request::add_custom_metadata("uid", auth.user_id());
46+
if let Some(id) = auth.api_token_id() {
47+
log_request::add_custom_metadata("tokenid", id);
48+
}
49+
50+
if !self.allow_token && auth.token_id.is_some() {
51+
let error_message = "API Token authentication was explicitly disallowed for this API";
52+
return Err(internal(error_message).chain(forbidden()));
53+
}
54+
55+
Ok(auth)
56+
}
57+
}
58+
1359
#[derive(Debug)]
1460
pub struct AuthenticatedUser {
1561
user: User,
@@ -28,18 +74,6 @@ impl AuthenticatedUser {
2874
pub fn user(self) -> User {
2975
self.user
3076
}
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-
}
4377
}
4478

4579
fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
@@ -85,37 +119,3 @@ fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
85119
// Unable to authenticate the user
86120
return Err(internal("no cookie session or auth header found").chain(forbidden()));
87121
}
88-
89-
pub trait UserAuthenticationExt {
90-
fn authenticate(&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(&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-
}

src/controllers.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ mod prelude {
1515
pub use conduit::{header, RequestExt, StatusCode};
1616
pub use conduit_router::RequestParams;
1717

18-
pub use crate::auth::UserAuthenticationExt;
1918
pub use crate::db::RequestTransaction;
2019
pub use crate::middleware::app::RequestApp;
2120
pub use crate::util::errors::{cargo_err, AppError, AppResult}; // TODO: Remove cargo_err from here

src/controllers/crate_owner_invitation.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::frontend_prelude::*;
22

3+
use crate::auth::AuthCheck;
34
use crate::auth::AuthenticatedUser;
45
use crate::controllers::helpers::pagination::{Page, PaginationOptions};
56
use crate::models::{Crate, CrateOwnerInvitation, Rights, User};
@@ -16,7 +17,7 @@ use std::collections::{HashMap, HashSet};
1617

1718
/// Handles the `GET /api/v1/me/crate_owner_invitations` route.
1819
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
19-
let auth = req.authenticate()?.forbid_api_token_auth()?;
20+
let auth = AuthCheck::only_cookie().check(req)?;
2021
let user_id = auth.user_id();
2122

2223
let PrivateListResponse {
@@ -52,7 +53,7 @@ pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
5253

5354
/// Handles the `GET /api/private/crate_owner_invitations` route.
5455
pub fn private_list(req: &mut dyn RequestExt) -> EndpointResult {
55-
let auth = req.authenticate()?.forbid_api_token_auth()?;
56+
let auth = AuthCheck::only_cookie().check(req)?;
5657

5758
let filter = if let Some(crate_name) = req.query().get("crate_name") {
5859
ListFilter::CrateName(crate_name.clone())
@@ -255,7 +256,9 @@ pub fn handle_invite(req: &mut dyn RequestExt) -> EndpointResult {
255256
serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;
256257

257258
let crate_invite = crate_invite.crate_owner_invite;
258-
let user_id = req.authenticate()?.user_id();
259+
260+
let auth = AuthCheck::default().check(req)?;
261+
let user_id = auth.user_id();
259262
let conn = &*req.db_write()?;
260263
let config = &req.app().config;
261264

src/controllers/krate/follow.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Endpoints for managing a per user list of followed crates
22
3+
use crate::auth::AuthCheck;
34
use diesel::associations::Identifiable;
45

56
use crate::controllers::frontend_prelude::*;
@@ -21,7 +22,7 @@ fn follow_target(
2122

2223
/// Handles the `PUT /crates/:crate_id/follow` route.
2324
pub fn follow(req: &mut dyn RequestExt) -> EndpointResult {
24-
let user_id = req.authenticate()?.user_id();
25+
let user_id = AuthCheck::default().check(req)?.user_id();
2526
let conn = req.db_write()?;
2627
let follow = follow_target(req, &conn, user_id)?;
2728
diesel::insert_into(follows::table)
@@ -34,7 +35,7 @@ pub fn follow(req: &mut dyn RequestExt) -> EndpointResult {
3435

3536
/// Handles the `DELETE /crates/:crate_id/follow` route.
3637
pub fn unfollow(req: &mut dyn RequestExt) -> EndpointResult {
37-
let user_id = req.authenticate()?.user_id();
38+
let user_id = AuthCheck::default().check(req)?.user_id();
3839
let conn = req.db_write()?;
3940
let follow = follow_target(req, &conn, user_id)?;
4041
diesel::delete(&follow).execute(&*conn)?;
@@ -46,7 +47,7 @@ pub fn unfollow(req: &mut dyn RequestExt) -> EndpointResult {
4647
pub fn following(req: &mut dyn RequestExt) -> EndpointResult {
4748
use diesel::dsl::exists;
4849

49-
let user_id = req.authenticate()?.forbid_api_token_auth()?.user_id();
50+
let user_id = AuthCheck::only_cookie().check(req)?.user_id();
5051
let conn = req.db_read_prefer_primary()?;
5152
let follow = follow_target(req, &conn, user_id)?;
5253
let following =

src/controllers/krate/owners.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! All routes related to managing owners of a crate
22
3+
use crate::auth::AuthCheck;
34
use crate::controllers::prelude::*;
45
use crate::models::{Crate, Owner, Rights, Team, User};
56
use crate::views::EncodableOwner;
@@ -79,13 +80,14 @@ fn parse_owners_request(req: &mut dyn RequestExt) -> AppResult<Vec<String>> {
7980
}
8081

8182
fn modify_owners(req: &mut dyn RequestExt, add: bool) -> EndpointResult {
82-
let authenticated_user = req.authenticate()?;
83+
let auth = AuthCheck::default().check(req)?;
84+
8385
let logins = parse_owners_request(req)?;
8486
let app = req.app();
8587
let crate_name = &req.params()["crate_id"];
8688

8789
let conn = req.db_write()?;
88-
let user = authenticated_user.user();
90+
let user = auth.user();
8991

9092
conn.transaction(|| {
9193
let krate: Crate = Crate::by_name(crate_name).first(&*conn)?;

src/controllers/krate/publish.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Functionality related to publishing a new crate or version of a crate.
22
3+
use crate::auth::AuthCheck;
34
use flate2::read::GzDecoder;
45
use hex::ToHex;
56
use sha2::{Digest, Sha256};
@@ -64,9 +65,9 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
6465
add_custom_metadata("crate_version", new_crate.vers.to_string());
6566

6667
let conn = app.primary_database.get()?;
67-
let ids = req.authenticate()?;
68-
let api_token_id = ids.api_token_id();
69-
let user = ids.user();
68+
let auth = AuthCheck::default().check(req)?;
69+
let api_token_id = auth.api_token_id();
70+
let user = auth.user();
7071

7172
let verified_email_address = user.verified_email(&conn)?;
7273
let verified_email_address = verified_email_address.ok_or_else(|| {

src/controllers/krate/search.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Endpoint for searching and discovery functionality
22
3+
use crate::auth::AuthCheck;
34
use diesel::dsl::*;
45
use diesel::sql_types::Array;
56
use diesel_full_text_search::*;
@@ -186,7 +187,8 @@ pub fn search(req: &mut dyn RequestExt) -> EndpointResult {
186187
// Calculating the total number of results with filters is not supported yet.
187188
supports_seek = false;
188189

189-
let user_id = req.authenticate()?.user_id();
190+
let user_id = AuthCheck::default().check(req)?.user_id();
191+
190192
query = query.filter(
191193
crates::id.eq_any(
192194
follows::table

src/controllers/token.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ use crate::schema::api_tokens;
55
use crate::util::read_fill;
66
use crate::views::EncodableApiTokenWithToken;
77

8+
use crate::auth::AuthCheck;
89
use conduit::{Body, Response};
910
use serde_json as json;
1011

1112
/// Handles the `GET /me/tokens` route.
1213
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
13-
let authenticated_user = req.authenticate()?.forbid_api_token_auth()?;
14+
let auth = AuthCheck::only_cookie().check(req)?;
1415
let conn = req.db_read_prefer_primary()?;
15-
let user = authenticated_user.user();
16+
let user = auth.user();
1617

1718
let tokens: Vec<ApiToken> = ApiToken::belonging_to(&user)
1819
.filter(api_tokens::revoked.eq(false))
@@ -59,15 +60,15 @@ pub fn new(req: &mut dyn RequestExt) -> EndpointResult {
5960
return Err(bad_request("name must have a value"));
6061
}
6162

62-
let authenticated_user = req.authenticate()?;
63-
if authenticated_user.api_token_id().is_some() {
63+
let auth = AuthCheck::default().check(req)?;
64+
if auth.api_token_id().is_some() {
6465
return Err(bad_request(
6566
"cannot use an API token to create a new API token",
6667
));
6768
}
6869

6970
let conn = req.db_write()?;
70-
let user = authenticated_user.user();
71+
let user = auth.user();
7172

7273
let max_token_per_user = 500;
7374
let count: i64 = ApiToken::belonging_to(&user).count().get_result(&*conn)?;
@@ -90,9 +91,9 @@ pub fn revoke(req: &mut dyn RequestExt) -> EndpointResult {
9091
.parse::<i32>()
9192
.map_err(|e| bad_request(&format!("invalid token id: {e:?}")))?;
9293

93-
let authenticated_user = req.authenticate()?;
94+
let auth = AuthCheck::default().check(req)?;
9495
let conn = req.db_write()?;
95-
let user = authenticated_user.user();
96+
let user = auth.user();
9697
diesel::update(ApiToken::belonging_to(&user).find(id))
9798
.set(api_tokens::revoked.eq(true))
9899
.execute(&*conn)?;
@@ -102,8 +103,8 @@ pub fn revoke(req: &mut dyn RequestExt) -> EndpointResult {
102103

103104
/// Handles the `DELETE /tokens/current` route.
104105
pub fn revoke_current(req: &mut dyn RequestExt) -> EndpointResult {
105-
let authenticated_user = req.authenticate()?;
106-
let api_token_id = authenticated_user
106+
let auth = AuthCheck::default().check(req)?;
107+
let api_token_id = auth
107108
.api_token_id()
108109
.ok_or_else(|| bad_request("token not provided"))?;
109110

src/controllers/user/me.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::auth::AuthCheck;
12
use std::collections::HashMap;
23

34
use crate::controllers::frontend_prelude::*;
@@ -13,7 +14,7 @@ use crate::views::{EncodableMe, EncodablePrivateUser, EncodableVersion, OwnedCra
1314

1415
/// Handles the `GET /me` route.
1516
pub fn me(req: &mut dyn RequestExt) -> EndpointResult {
16-
let user_id = req.authenticate()?.forbid_api_token_auth()?.user_id();
17+
let user_id = AuthCheck::only_cookie().check(req)?.user_id();
1718
let conn = req.db_read_prefer_primary()?;
1819

1920
let (user, verified, email, verification_sent): (User, Option<bool>, Option<String>, bool) =
@@ -54,8 +55,8 @@ pub fn me(req: &mut dyn RequestExt) -> EndpointResult {
5455
pub fn updates(req: &mut dyn RequestExt) -> EndpointResult {
5556
use diesel::dsl::any;
5657

57-
let authenticated_user = req.authenticate()?.forbid_api_token_auth()?;
58-
let user = authenticated_user.user();
58+
let auth = AuthCheck::only_cookie().check(req)?;
59+
let user = auth.user();
5960

6061
let followed_crates = Follow::belonging_to(&user).select(follows::crate_id);
6162
let query = versions::table
@@ -96,14 +97,14 @@ pub fn update_user(req: &mut dyn RequestExt) -> EndpointResult {
9697
use self::emails::user_id;
9798
use diesel::insert_into;
9899

99-
let authenticated_user = req.authenticate()?;
100+
let auth = AuthCheck::default().check(req)?;
100101

101102
let mut body = String::new();
102103
req.body().read_to_string(&mut body)?;
103104

104105
let param_user_id = &req.params()["user_id"];
105106
let conn = req.db_write()?;
106-
let user = authenticated_user.user();
107+
let user = auth.user();
107108

108109
// need to check if current user matches user to be updated
109110
if &user.id.to_string() != param_user_id {
@@ -188,9 +189,11 @@ pub fn regenerate_token_and_send(req: &mut dyn RequestExt) -> EndpointResult {
188189
let param_user_id = req.params()["user_id"]
189190
.parse::<i32>()
190191
.map_err(|err| err.chain(bad_request("invalid user_id")))?;
191-
let authenticated_user = req.authenticate()?;
192+
193+
let auth = AuthCheck::default().check(req)?;
194+
192195
let conn = req.db_write()?;
193-
let user = authenticated_user.user();
196+
let user = auth.user();
194197

195198
// need to check if current user matches user to be updated
196199
if user.id != param_user_id {
@@ -230,7 +233,7 @@ pub fn update_email_notifications(req: &mut dyn RequestExt) -> EndpointResult {
230233
.map(|c| (c.id, c.email_notifications))
231234
.collect();
232235

233-
let user_id = req.authenticate()?.user_id();
236+
let user_id = AuthCheck::default().check(req)?.user_id();
234237
let conn = req.db_write()?;
235238

236239
// Build inserts from existing crates belonging to the current user

src/controllers/version/yank.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Endpoints for yanking and unyanking specific versions of crates
22
3+
use crate::auth::AuthCheck;
34
use swirl::Job;
45

56
use super::{extract_crate_name_and_semver, version_and_crate};
@@ -31,13 +32,13 @@ pub fn unyank(req: &mut dyn RequestExt) -> EndpointResult {
3132
fn modify_yank(req: &mut dyn RequestExt, yanked: bool) -> EndpointResult {
3233
// FIXME: Should reject bad requests before authentication, but can't due to
3334
// lifetime issues with `req`.
34-
let authenticated_user = req.authenticate()?;
35+
let auth = AuthCheck::default().check(req)?;
3536
let (crate_name, semver) = extract_crate_name_and_semver(req)?;
3637

3738
let conn = req.db_write()?;
3839
let (version, krate) = version_and_crate(&conn, crate_name, semver)?;
39-
let api_token_id = authenticated_user.api_token_id();
40-
let user = authenticated_user.user();
40+
let api_token_id = auth.api_token_id();
41+
let user = auth.user();
4142
let owners = krate.owners(&conn)?;
4243

4344
if user.rights(req.app(), &owners)? < Rights::Publish {

0 commit comments

Comments
 (0)