Skip to content

Commit e96846d

Browse files
committed
publish: Allow empty auth scheme for Trusted Publishing tokens
... by using the new `AuthHeader` extractor, which also slightly improves our error messages.
1 parent a74cf67 commit e96846d

File tree

2 files changed

+27
-32
lines changed

2 files changed

+27
-32
lines changed

src/controllers/krate/publish.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Functionality related to publishing a new crate or version of a crate.
22
33
use crate::app::AppState;
4-
use crate::auth::{AuthCheck, Authentication};
4+
use crate::auth::{AuthCheck, AuthHeader, Authentication};
55
use crate::worker::jobs::{
66
self, CheckTyposquat, SendPublishNotificationsJob, UpdateDefaultVersion,
77
};
@@ -19,8 +19,9 @@ use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
1919
use futures_util::TryFutureExt;
2020
use futures_util::TryStreamExt;
2121
use hex::ToHex;
22+
use http::StatusCode;
2223
use http::request::Parts;
23-
use http::{StatusCode, header};
24+
use secrecy::ExposeSecret;
2425
use sha2::{Digest, Sha256};
2526
use std::collections::HashMap;
2627
use tokio::io::{AsyncRead, AsyncReadExt};
@@ -146,21 +147,20 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
146147
.await
147148
.optional()?;
148149

149-
// Trusted publishing tokens are distinguished from regular crates.io API
150-
// tokens because they use the `Bearer` auth scheme, so we look for that
151-
// specific prefix.
152-
let trustpub_token = req
153-
.headers
154-
.get(header::AUTHORIZATION)
155-
.and_then(|h| {
156-
let mut split = h.as_bytes().splitn(2, |b| *b == b' ');
157-
Some((split.next()?, split.next()?))
150+
let auth_header = AuthHeader::optional_from_request_parts(&req).await?;
151+
let trustpub_token = auth_header
152+
.and_then(|auth| {
153+
let token = auth.token().expose_secret();
154+
if !token.starts_with(AccessToken::PREFIX) {
155+
return None;
156+
}
157+
158+
Some(AccessToken::from_byte_str(token.as_bytes()).map_err(|_| {
159+
let message = "Invalid `Authorization` header: Failed to parse token";
160+
custom(StatusCode::UNAUTHORIZED, message)
161+
}))
158162
})
159-
.filter(|(scheme, _token)| scheme.eq_ignore_ascii_case(b"Bearer"))
160-
.map(|(_scheme, token)| token.trim_ascii())
161-
.map(AccessToken::from_byte_str)
162-
.transpose()
163-
.map_err(|_| forbidden("Invalid authentication token"))?;
163+
.transpose()?;
164164

165165
let auth = if let Some(trustpub_token) = trustpub_token {
166166
let Some(existing_crate) = &existing_crate else {

src/tests/krate/publish/trustpub.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ async fn test_full_flow() -> anyhow::Result<()> {
122122

123123
// Step 4: Publish a new version of the crate using the temporary access token
124124

125-
let header = format!("Bearer {}", token);
126-
let oidc_token_client = MockTokenUser::with_auth_header(header, app.clone());
125+
let oidc_token_client = MockTokenUser::with_auth_header(token.to_string(), app.clone());
127126

128127
let pb = PublishBuilder::new(CRATE_NAME, "1.1.0");
129128
let response = oidc_token_client.publish_crate(pb).await;
@@ -186,8 +185,7 @@ async fn test_happy_path() -> anyhow::Result<()> {
186185

187186
let token = new_token(&mut conn, krate.id).await?;
188187

189-
let header = format!("Bearer {}", token);
190-
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
188+
let oidc_token_client = MockTokenUser::with_auth_header(token, app);
191189

192190
let pb = PublishBuilder::new(&krate.name, "1.1.0");
193191
let response = oidc_token_client.publish_crate(pb).await;
@@ -226,15 +224,15 @@ async fn test_happy_path_with_fancy_auth_header() -> anyhow::Result<()> {
226224
}
227225

228226
#[tokio::test(flavor = "multi_thread")]
229-
async fn test_invalid_authorization_header_format() -> anyhow::Result<()> {
227+
async fn test_invalid_token_format() -> anyhow::Result<()> {
230228
let (app, _client, cookie_client) = TestApp::full().with_user().await;
231229

232230
let mut conn = app.db_conn().await;
233231

234232
let owner_id = cookie_client.as_model().id;
235233
let krate = CrateBuilder::new("foo", owner_id).build(&mut conn).await?;
236234

237-
// Create a client with an invalid authorization header (missing "Bearer " prefix)
235+
// Create a client with an invalid authorization header (missing token prefix)
238236
let header = "invalid-format".to_string();
239237
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
240238

@@ -247,22 +245,22 @@ async fn test_invalid_authorization_header_format() -> anyhow::Result<()> {
247245
}
248246

249247
#[tokio::test(flavor = "multi_thread")]
250-
async fn test_invalid_token_format() -> anyhow::Result<()> {
248+
async fn test_invalid_bearer_token_format() -> anyhow::Result<()> {
251249
let (app, _client, cookie_client) = TestApp::full().with_user().await;
252250

253251
let mut conn = app.db_conn().await;
254252

255253
let owner_id = cookie_client.as_model().id;
256254
let krate = CrateBuilder::new("foo", owner_id).build(&mut conn).await?;
257255

258-
// Create a client with an invalid authorization header (missing "Bearer " prefix)
256+
// Create a client with an invalid authorization header (missing token prefix)
259257
let header = "Bearer invalid-token".to_string();
260258
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
261259

262260
let pb = PublishBuilder::new(&krate.name, "1.1.0");
263261
let response = oidc_token_client.publish_crate(pb).await;
264-
assert_snapshot!(response.status(), @"403 Forbidden");
265-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"Invalid authentication token"}]}"#);
262+
assert_snapshot!(response.status(), @"401 Unauthorized");
263+
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"The given API token does not match the format used by crates.io. Tokens generated before 2020-07-14 were generated with an insecure random number generator, and have been revoked. You can generate a new token at https://crates.io/me. For more information please see https://blog.rust-lang.org/2020/07/14/crates-io-security-advisory.html. We apologize for any inconvenience."}]}"#);
266264

267265
Ok(())
268266
}
@@ -278,8 +276,7 @@ async fn test_non_existent_token() -> anyhow::Result<()> {
278276

279277
// Generate a valid token format, but it doesn't exist in the database
280278
let (token, _) = generate_token();
281-
let header = format!("Bearer {}", token);
282-
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
279+
let oidc_token_client = MockTokenUser::with_auth_header(token, app);
283280

284281
let pb = PublishBuilder::new(&krate.name, "1.1.0");
285282
let response = oidc_token_client.publish_crate(pb).await;
@@ -295,8 +292,7 @@ async fn test_non_existent_token_with_new_crate() -> anyhow::Result<()> {
295292

296293
// Generate a valid token format, but it doesn't exist in the database
297294
let (token, _) = generate_token();
298-
let header = format!("Bearer {}", token);
299-
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
295+
let oidc_token_client = MockTokenUser::with_auth_header(token, app);
300296

301297
let pb = PublishBuilder::new("foo", "1.0.0");
302298
let response = oidc_token_client.publish_crate(pb).await;
@@ -316,8 +312,7 @@ async fn test_token_for_wrong_crate() -> anyhow::Result<()> {
316312
let krate = CrateBuilder::new("foo", owner_id).build(&mut conn).await?;
317313
let token = new_token(&mut conn, krate.id).await?;
318314

319-
let header = format!("Bearer {}", token);
320-
let oidc_token_client = MockTokenUser::with_auth_header(header, app);
315+
let oidc_token_client = MockTokenUser::with_auth_header(token, app);
321316

322317
let krate = CrateBuilder::new("bar", owner_id).build(&mut conn).await?;
323318

0 commit comments

Comments
 (0)