-
Notifications
You must be signed in to change notification settings - Fork 649
Defer user authentication until requested by endpoint #2077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ use crate::controllers::cargo_prelude::*; | |
use crate::git; | ||
use crate::models::dependency; | ||
use crate::models::{ | ||
insert_version_owner_action, Badge, Category, Keyword, NewCrate, NewVersion, Rights, User, | ||
insert_version_owner_action, Badge, Category, Keyword, NewCrate, NewVersion, Rights, | ||
VersionAction, | ||
}; | ||
|
||
|
@@ -39,9 +39,11 @@ pub fn publish(req: &mut dyn Request) -> AppResult<Response> { | |
// - Then the .crate tarball length is passed to the upload_crate function where the actual | ||
// file is read and uploaded. | ||
|
||
let (new_crate, user) = parse_new_headers(req)?; | ||
let new_crate = parse_new_headers(req)?; | ||
|
||
let conn = app.diesel_database.get()?; | ||
let ids = req.authenticate(&conn)?; | ||
let user = ids.find_user(&conn)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOVE that this is out of |
||
|
||
let verified_email_address = user.verified_email(&conn)?; | ||
let verified_email_address = verified_email_address.ok_or_else(|| { | ||
|
@@ -150,7 +152,7 @@ pub fn publish(req: &mut dyn Request) -> AppResult<Response> { | |
&conn, | ||
version.id, | ||
user.id, | ||
req.authentication_source()?.api_token_id(), | ||
ids.api_token_id(), | ||
VersionAction::Publish, | ||
)?; | ||
|
||
|
@@ -223,9 +225,8 @@ pub fn publish(req: &mut dyn Request) -> AppResult<Response> { | |
/// Used by the `krate::new` function. | ||
/// | ||
/// This function parses the JSON headers to interpret the data and validates | ||
/// the data during and after the parsing. Returns crate metadata and user | ||
/// information. | ||
fn parse_new_headers(req: &mut dyn Request) -> AppResult<(EncodableCrateUpload, User)> { | ||
/// the data during and after the parsing. Returns crate metadata. | ||
fn parse_new_headers(req: &mut dyn Request) -> AppResult<EncodableCrateUpload> { | ||
// Read the json upload request | ||
let metadata_length = u64::from(read_le_u32(req.body())?); | ||
req.mut_extensions().insert(metadata_length); | ||
|
@@ -264,6 +265,5 @@ fn parse_new_headers(req: &mut dyn Request) -> AppResult<(EncodableCrateUpload, | |
))); | ||
} | ||
|
||
let user = req.user()?; | ||
Ok((new, user.clone())) | ||
Ok(new) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use super::prelude::*; | ||
|
||
use crate::middleware::current_user::TrustedUserId; | ||
use crate::models::{ApiToken, User}; | ||
use crate::util::errors::{internal, AppError, AppResult, ChainError, Unauthorized}; | ||
|
||
#[derive(Debug)] | ||
pub struct AuthenticatedUser { | ||
user_id: i32, | ||
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 find_user(&self, conn: &PgConnection) -> AppResult<User> { | ||
User::find(conn, self.user_id()) | ||
.chain_error(|| internal("user_id from cookie or token not found in database")) | ||
} | ||
} | ||
|
||
impl<'a> UserAuthenticationExt for dyn Request + 'a { | ||
/// Obtain `AuthenticatedUser` for the request or return an `Unauthorized` error | ||
fn authenticate(&self, conn: &PgConnection) -> AppResult<AuthenticatedUser> { | ||
if let Some(id) = self.extensions().find::<TrustedUserId>() { | ||
// A trusted user_id was provided by a signed cookie (or a test `MockCookieUser`) | ||
Ok(AuthenticatedUser { | ||
user_id: id.0, | ||
token_id: None, | ||
}) | ||
} else { | ||
// Otherwise, look for an `Authorization` header on the request | ||
if let Some(headers) = self.headers().find("Authorization") { | ||
ApiToken::find_by_api_token(conn, headers[0]) | ||
.map(|token| AuthenticatedUser { | ||
user_id: token.user_id, | ||
token_id: Some(token.id), | ||
}) | ||
// Convert a NotFound (or other database error) into Unauthorized | ||
.map_err(|_| Box::new(Unauthorized) as Box<dyn AppError>) | ||
} else { | ||
// Unable to authenticate the user | ||
Err(Box::new(Unauthorized)) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,15 +5,17 @@ pub mod yank; | |
|
||
use super::prelude::*; | ||
|
||
use crate::db::DieselPooledConn; | ||
use crate::models::{Crate, CrateVersions, Version}; | ||
use crate::schema::versions; | ||
|
||
fn version_and_crate(req: &mut dyn Request) -> AppResult<(Version, Crate)> { | ||
fn version_and_crate(req: &dyn Request) -> AppResult<(DieselPooledConn<'_>, Version, Crate)> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is a little strange now that it returns a connection too, but 🤷♀ it's not exactly the cleanest abstraction to begin with anyway given that half the time we don't even use the returned crate. Always more to do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would be cleaner to obtain a connection and pass it to this function, but it just
Yup! |
||
let crate_name = &req.params()["crate_id"]; | ||
let semver = &req.params()["version"]; | ||
if semver::Version::parse(semver).is_err() { | ||
return Err(cargo_err(&format_args!("invalid semver: {}", semver))); | ||
}; | ||
|
||
let conn = req.db_conn()?; | ||
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?; | ||
let version = krate | ||
|
@@ -26,5 +28,6 @@ fn version_and_crate(req: &mut dyn Request) -> AppResult<(Version, Crate)> { | |
crate_name, semver | ||
)) | ||
})?; | ||
Ok((version, krate)) | ||
|
||
Ok((conn, version, krate)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that this abstraction gets rid of a
User
lookup in cases like this spot when it's not needed 👍