From ffe9537b82043aca9970f649cb366b8da1c52ce4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 15:39:36 +0100 Subject: [PATCH 1/7] models/token: Extract `NewApiToken` struct --- src/models/token.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/models/token.rs b/src/models/token.rs index bc1d320a558..53134f69fa4 100644 --- a/src/models/token.rs +++ b/src/models/token.rs @@ -12,6 +12,19 @@ use crate::schema::api_tokens; use crate::util::rfc3339; use crate::util::token::{HashedToken, PlainToken}; +#[derive(Debug, Insertable)] +#[diesel(table_name = api_tokens, check_for_backend(diesel::pg::Pg))] +pub struct NewApiToken { + pub user_id: i32, + pub name: String, + pub token: HashedToken, + /// `None` or a list of crate scope patterns (see RFC #2947) + pub crate_scopes: Option>, + /// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947) + pub endpoint_scopes: Option>, + pub expired_at: Option, +} + /// The model representing a row in the `api_tokens` database table. #[derive(Debug, Identifiable, Queryable, Selectable, Associations, Serialize)] #[diesel(belongs_to(User))] @@ -54,15 +67,17 @@ impl ApiToken { ) -> QueryResult { let token = PlainToken::generate(); + let new_token = NewApiToken { + user_id, + name: name.to_string(), + token: token.hashed(), + crate_scopes, + endpoint_scopes, + expired_at, + }; + let model: ApiToken = diesel::insert_into(api_tokens::table) - .values(( - api_tokens::user_id.eq(user_id), - api_tokens::name.eq(name), - api_tokens::token.eq(token.hashed()), - api_tokens::crate_scopes.eq(crate_scopes), - api_tokens::endpoint_scopes.eq(endpoint_scopes), - api_tokens::expired_at.eq(expired_at), - )) + .values(new_token) .returning(ApiToken::as_returning()) .get_result(conn) .await?; From 74daa6ce26e9e6e4e72d0ea21b8768bd1c53a03c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 15:41:13 +0100 Subject: [PATCH 2/7] models/token: Extract `NewApiToken::insert()` fn --- src/models/token.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/models/token.rs b/src/models/token.rs index 53134f69fa4..38d7ae8f2c7 100644 --- a/src/models/token.rs +++ b/src/models/token.rs @@ -25,6 +25,16 @@ pub struct NewApiToken { pub expired_at: Option, } +impl NewApiToken { + pub async fn insert(&self, conn: &mut AsyncPgConnection) -> QueryResult { + diesel::insert_into(api_tokens::table) + .values(self) + .returning(ApiToken::as_returning()) + .get_result(conn) + .await + } +} + /// The model representing a row in the `api_tokens` database table. #[derive(Debug, Identifiable, Queryable, Selectable, Associations, Serialize)] #[diesel(belongs_to(User))] @@ -76,15 +86,9 @@ impl ApiToken { expired_at, }; - let model: ApiToken = diesel::insert_into(api_tokens::table) - .values(new_token) - .returning(ApiToken::as_returning()) - .get_result(conn) - .await?; - Ok(CreatedApiToken { plaintext: token, - model, + model: new_token.insert(conn).await?, }) } From 19643d470d8e4c5c19e84d1361c7874d5631c537 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 15:45:55 +0100 Subject: [PATCH 3/7] models/token: Derive `bon::Builder` for `NewApiToken` struct --- src/models/token.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/models/token.rs b/src/models/token.rs index 38d7ae8f2c7..3f7a1557138 100644 --- a/src/models/token.rs +++ b/src/models/token.rs @@ -1,5 +1,6 @@ mod scopes; +use bon::Builder; use chrono::NaiveDateTime; use diesel::dsl::now; use diesel::prelude::*; @@ -12,11 +13,13 @@ use crate::schema::api_tokens; use crate::util::rfc3339; use crate::util::token::{HashedToken, PlainToken}; -#[derive(Debug, Insertable)] +#[derive(Debug, Insertable, Builder)] #[diesel(table_name = api_tokens, check_for_backend(diesel::pg::Pg))] pub struct NewApiToken { pub user_id: i32, + #[builder(into)] pub name: String, + #[builder(default = PlainToken::generate().hashed())] pub token: HashedToken, /// `None` or a list of crate scope patterns (see RFC #2947) pub crate_scopes: Option>, @@ -77,14 +80,14 @@ impl ApiToken { ) -> QueryResult { let token = PlainToken::generate(); - let new_token = NewApiToken { - user_id, - name: name.to_string(), - token: token.hashed(), - crate_scopes, - endpoint_scopes, - expired_at, - }; + let new_token = NewApiToken::builder() + .user_id(user_id) + .name(name) + .token(token.hashed()) + .maybe_crate_scopes(crate_scopes) + .maybe_endpoint_scopes(endpoint_scopes) + .maybe_expired_at(expired_at) + .build(); Ok(CreatedApiToken { plaintext: token, From 761116a8711083bc3c4579ebf26c2810a1af9740 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 16:00:03 +0100 Subject: [PATCH 4/7] models/token: Inline `ApiToken::insert()` fn --- src/models/token.rs | 9 --- src/tests/routes/me/tokens/create.rs | 6 +- src/tests/routes/me/tokens/get.rs | 39 +++++----- src/tests/routes/me/tokens/list.rs | 105 ++++++++++++--------------- 4 files changed, 71 insertions(+), 88 deletions(-) diff --git a/src/models/token.rs b/src/models/token.rs index 3f7a1557138..de6794ae176 100644 --- a/src/models/token.rs +++ b/src/models/token.rs @@ -61,15 +61,6 @@ pub struct ApiToken { } impl ApiToken { - /// Generates a new named API token for a user - pub async fn insert( - conn: &mut AsyncPgConnection, - user_id: i32, - name: &str, - ) -> QueryResult { - Self::insert_with_scopes(conn, user_id, name, None, None, None).await - } - pub async fn insert_with_scopes( conn: &mut AsyncPgConnection, user_id: i32, diff --git a/src/tests/routes/me/tokens/create.rs b/src/tests/routes/me/tokens/create.rs index 529295631f4..6ca675ed6d4 100644 --- a/src/tests/routes/me/tokens/create.rs +++ b/src/tests/routes/me/tokens/create.rs @@ -1,4 +1,4 @@ -use crate::models::token::{CrateScope, EndpointScope}; +use crate::models::token::{CrateScope, EndpointScope, NewApiToken}; use crate::models::ApiToken; use crate::tests::util::insta::{self, assert_json_snapshot}; use crate::tests::util::{RequestHelper, TestApp}; @@ -46,7 +46,9 @@ async fn create_token_exceeded_tokens_per_user() { let id = user.as_model().id; for i in 0..1000 { - assert_ok!(ApiToken::insert(&mut conn, id, &format!("token {i}")).await); + let name = format!("token {i}"); + let new_token = NewApiToken::builder().name(name).user_id(id).build(); + assert_ok!(new_token.insert(&mut conn).await); } let response = user.put::<()>("/api/v1/me/tokens", NEW_BAR).await; diff --git a/src/tests/routes/me/tokens/get.rs b/src/tests/routes/me/tokens/get.rs index 56d06be1466..110fcc02c61 100644 --- a/src/tests/routes/me/tokens/get.rs +++ b/src/tests/routes/me/tokens/get.rs @@ -1,5 +1,4 @@ -use crate::models::token::{CrateScope, EndpointScope}; -use crate::models::ApiToken; +use crate::models::token::{CrateScope, EndpointScope, NewApiToken}; use crate::tests::util::{RequestHelper, TestApp}; use chrono::{Duration, Utc}; use http::StatusCode; @@ -31,23 +30,22 @@ async fn show_token_with_scopes() { let user_model = user.as_model(); let id = user_model.id; - assert_ok!(ApiToken::insert(&mut conn, id, "bar").await); - let token = assert_ok!( - ApiToken::insert_with_scopes( - &mut conn, - id, - "baz", - Some(vec![ - CrateScope::try_from("serde").unwrap(), - CrateScope::try_from("serde-*").unwrap() - ]), - Some(vec![EndpointScope::PublishUpdate]), - Some((Utc::now() - Duration::days(31)).naive_utc()), - ) - .await - ); + let new_token = NewApiToken::builder().name("bar").user_id(id).build(); + assert_ok!(new_token.insert(&mut conn).await); - let url = format!("/api/v1/me/tokens/{}", token.model.id); + let new_token = NewApiToken::builder() + .name("baz") + .user_id(id) + .crate_scopes(vec![ + CrateScope::try_from("serde").unwrap(), + CrateScope::try_from("serde-*").unwrap(), + ]) + .endpoint_scopes(vec![EndpointScope::PublishUpdate]) + .expired_at((Utc::now() - Duration::days(31)).naive_utc()) + .build(); + let token = assert_ok!(new_token.insert(&mut conn).await); + + let url = format!("/api/v1/me/tokens/{}", token.id); let response = user.get::<()>(&url).await; assert_eq!(response.status(), StatusCode::OK); assert_json_snapshot!(response.json(), { @@ -70,9 +68,10 @@ async fn show_other_user_token() { let user2 = app.db_new_user("baz").await; let user2 = user2.as_model(); - let token = assert_ok!(ApiToken::insert(&mut conn, user2.id, "bar").await); + let new_token = NewApiToken::builder().name("bar").user_id(user2.id).build(); + let token = assert_ok!(new_token.insert(&mut conn).await); - let url = format!("/api/v1/me/tokens/{}", token.model.id); + let url = format!("/api/v1/me/tokens/{}", token.id); let response = user1.get::<()>(&url).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); } diff --git a/src/tests/routes/me/tokens/list.rs b/src/tests/routes/me/tokens/list.rs index ec1eb6574c0..56f46e4cb2f 100644 --- a/src/tests/routes/me/tokens/list.rs +++ b/src/tests/routes/me/tokens/list.rs @@ -1,5 +1,4 @@ -use crate::models::token::{CrateScope, EndpointScope}; -use crate::models::ApiToken; +use crate::models::token::{CrateScope, EndpointScope, NewApiToken}; use crate::tests::util::insta::{self, assert_json_snapshot}; use crate::tests::util::{RequestHelper, TestApp}; use chrono::{Duration, Utc}; @@ -33,32 +32,26 @@ async fn list_tokens() { let mut conn = app.db_conn().await; let id = user.as_model().id; - assert_ok!(ApiToken::insert(&mut conn, id, "bar").await); - assert_ok!( - ApiToken::insert_with_scopes( - &mut conn, - id, - "baz", - Some(vec![ - CrateScope::try_from("serde").unwrap(), - CrateScope::try_from("serde-*").unwrap() - ]), - Some(vec![EndpointScope::PublishUpdate]), - None - ) - .await - ); - assert_ok!( - ApiToken::insert_with_scopes( - &mut conn, - id, - "qux", - None, - None, - Some((Utc::now() - Duration::days(1)).naive_utc()), - ) - .await - ); + let new_token = NewApiToken::builder().name("bar").user_id(id).build(); + assert_ok!(new_token.insert(&mut conn).await); + + let new_token = NewApiToken::builder() + .name("baz") + .user_id(id) + .crate_scopes(vec![ + CrateScope::try_from("serde").unwrap(), + CrateScope::try_from("serde-*").unwrap(), + ]) + .endpoint_scopes(vec![EndpointScope::PublishUpdate]) + .build(); + assert_ok!(new_token.insert(&mut conn).await); + + let new_token = NewApiToken::builder() + .name("qux") + .user_id(id) + .expired_at((Utc::now() - Duration::days(1)).naive_utc()) + .build(); + assert_ok!(new_token.insert(&mut conn).await); let response = user.get::<()>("/api/v1/me/tokens").await; assert_eq!(response.status(), StatusCode::OK); @@ -80,32 +73,27 @@ async fn list_recently_expired_tokens() { let mut conn = app.db_conn().await; let id = user.as_model().id; - assert_ok!(ApiToken::insert(&mut conn, id, "bar").await); - assert_ok!( - ApiToken::insert_with_scopes( - &mut conn, - id, - "ancient", - Some(vec![ - CrateScope::try_from("serde").unwrap(), - CrateScope::try_from("serde-*").unwrap() - ]), - Some(vec![EndpointScope::PublishUpdate]), - Some((Utc::now() - Duration::days(31)).naive_utc()), - ) - .await - ); - assert_ok!( - ApiToken::insert_with_scopes( - &mut conn, - id, - "recent", - None, - None, - Some((Utc::now() - Duration::days(1)).naive_utc()), - ) - .await - ); + let new_token = NewApiToken::builder().name("bar").user_id(id).build(); + assert_ok!(new_token.insert(&mut conn).await); + + let new_token = NewApiToken::builder() + .name("ancient") + .user_id(id) + .crate_scopes(vec![ + CrateScope::try_from("serde").unwrap(), + CrateScope::try_from("serde-*").unwrap(), + ]) + .endpoint_scopes(vec![EndpointScope::PublishUpdate]) + .expired_at((Utc::now() - Duration::days(31)).naive_utc()) + .build(); + assert_ok!(new_token.insert(&mut conn).await); + + let new_token = NewApiToken::builder() + .name("recent") + .user_id(id) + .expired_at((Utc::now() - Duration::days(1)).naive_utc()) + .build(); + assert_ok!(new_token.insert(&mut conn).await); let response = user.get::<()>("/api/v1/me/tokens?expired_days=30").await; assert_eq!(response.status(), StatusCode::OK); @@ -131,8 +119,11 @@ async fn list_tokens_exclude_revoked() { let mut conn = app.db_conn().await; let id = user.as_model().id; - let token1 = assert_ok!(ApiToken::insert(&mut conn, id, "bar").await); - assert_ok!(ApiToken::insert(&mut conn, id, "baz").await); + let new_token = NewApiToken::builder().name("bar").user_id(id).build(); + let token1 = assert_ok!(new_token.insert(&mut conn).await); + + let new_token = NewApiToken::builder().name("baz").user_id(id).build(); + assert_ok!(new_token.insert(&mut conn).await); // List tokens expecting them all to be there. let response = user.get::<()>("/api/v1/me/tokens").await; @@ -143,7 +134,7 @@ async fn list_tokens_exclude_revoked() { // Revoke the first token. let response = user - .delete::<()>(&format!("/api/v1/me/tokens/{}", token1.model.id)) + .delete::<()>(&format!("/api/v1/me/tokens/{}", token1.id)) .await; assert_eq!(response.status(), StatusCode::OK); From a52cea253abf78dd6b8811f7141096ae33f6b68c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 16:07:20 +0100 Subject: [PATCH 5/7] models/token: Inline `ApiToken::insert_with_scopes()` fn --- src/controllers/token.rs | 33 +++++++++++++++++++++++---------- src/models.rs | 2 +- src/models/token.rs | 31 ------------------------------- src/tests/util.rs | 30 ++++++++++++++++++------------ src/views.rs | 6 +++--- 5 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/controllers/token.rs b/src/controllers/token.rs index 8dccba98831..7cef5ebc9b6 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -7,6 +7,7 @@ use crate::app::AppState; use crate::auth::AuthCheck; use crate::models::token::{CrateScope, EndpointScope}; use crate::util::errors::{bad_request, AppResult}; +use crate::util::token::PlainToken; use axum::extract::{Path, Query}; use axum::response::{IntoResponse, Response}; use axum::Json; @@ -148,15 +149,16 @@ pub async fn create_api_token( let recipient = user.email(&mut conn).await?; - let api_token = ApiToken::insert_with_scopes( - &mut conn, - user.id, - &new.api_token.name, - crate_scopes, - endpoint_scopes, - new.api_token.expired_at, - ) - .await?; + let plaintext = PlainToken::generate(); + + let new_token = crate::models::token::NewApiToken::builder() + .user_id(user.id) + .name(&new.api_token.name) + .token(plaintext.hashed()) + .maybe_crate_scopes(crate_scopes) + .maybe_endpoint_scopes(endpoint_scopes) + .maybe_expired_at(new.api_token.expired_at) + .build(); if let Some(recipient) = recipient { let email = NewTokenEmail { @@ -174,11 +176,22 @@ pub async fn create_api_token( } } - let api_token = EncodableApiTokenWithToken::from(api_token); + let created_token = CreatedApiToken { + plaintext, + model: new_token.insert(&mut conn).await?, + }; + + let api_token = EncodableApiTokenWithToken::from(created_token); Ok(json!({ "api_token": api_token })) } +#[derive(Debug)] +pub struct CreatedApiToken { + pub model: ApiToken, + pub plaintext: PlainToken, +} + /// Find API token by id. #[utoipa::path( get, diff --git a/src/models.rs b/src/models.rs index 5e71967d866..6c44a052356 100644 --- a/src/models.rs +++ b/src/models.rs @@ -14,7 +14,7 @@ pub use self::krate::{Crate, CrateName, NewCrate, RecentCrateDownloads}; pub use self::owner::{CrateOwner, Owner, OwnerKind}; pub use self::rights::Rights; pub use self::team::{NewTeam, Team}; -pub use self::token::{ApiToken, CreatedApiToken}; +pub use self::token::ApiToken; pub use self::user::{NewUser, User}; pub use self::version::{NewVersion, TopVersions, Version}; diff --git a/src/models/token.rs b/src/models/token.rs index de6794ae176..231a634982d 100644 --- a/src/models/token.rs +++ b/src/models/token.rs @@ -61,31 +61,6 @@ pub struct ApiToken { } impl ApiToken { - pub async fn insert_with_scopes( - conn: &mut AsyncPgConnection, - user_id: i32, - name: &str, - crate_scopes: Option>, - endpoint_scopes: Option>, - expired_at: Option, - ) -> QueryResult { - let token = PlainToken::generate(); - - let new_token = NewApiToken::builder() - .user_id(user_id) - .name(name) - .token(token.hashed()) - .maybe_crate_scopes(crate_scopes) - .maybe_endpoint_scopes(endpoint_scopes) - .maybe_expired_at(expired_at) - .build(); - - Ok(CreatedApiToken { - plaintext: token, - model: new_token.insert(conn).await?, - }) - } - pub async fn find_by_api_token( conn: &mut AsyncPgConnection, token: &HashedToken, @@ -120,12 +95,6 @@ impl ApiToken { } } -#[derive(Debug)] -pub struct CreatedApiToken { - pub model: ApiToken, - pub plaintext: PlainToken, -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/tests/util.rs b/src/tests/util.rs index 7491e9f8578..535e77c8d22 100644 --- a/src/tests/util.rs +++ b/src/tests/util.rs @@ -19,7 +19,7 @@ //! `MockCookieUser` and `MockTokenUser` provide an `as_model` function which returns a reference //! to the underlying database model value (`User` and `ApiToken` respectively). -use crate::models::{ApiToken, CreatedApiToken, User}; +use crate::models::{ApiToken, User}; use crate::tests::{ CategoryListResponse, CategoryResponse, CrateList, CrateResponse, GoodCrate, OwnerResp, OwnersResponse, VersionResponse, @@ -28,7 +28,7 @@ use std::future::Future; use http::{Method, Request}; -use crate::models::token::{CrateScope, EndpointScope}; +use crate::models::token::{CrateScope, EndpointScope, NewApiToken}; use crate::util::token::PlainToken; use axum::body::{Body, Bytes}; use axum::extract::connect_info::MockConnectInfo; @@ -50,6 +50,7 @@ mod mock_request; mod response; mod test_app; +use crate::controllers::token::CreatedApiToken; use mock_request::MockRequest; pub use mock_request::MockRequestExt; pub use response::Response; @@ -320,16 +321,21 @@ impl MockCookieUser { ) -> MockTokenUser { let mut conn = self.app().db_conn().await; - let token = ApiToken::insert_with_scopes( - &mut conn, - self.user.id, - name, - crate_scopes, - endpoint_scopes, - expired_at, - ) - .await - .unwrap(); + let plaintext = PlainToken::generate(); + + let new_token = NewApiToken::builder() + .user_id(self.user.id) + .name(name) + .token(plaintext.hashed()) + .maybe_crate_scopes(crate_scopes) + .maybe_endpoint_scopes(endpoint_scopes) + .maybe_expired_at(expired_at) + .build(); + + let token = CreatedApiToken { + plaintext, + model: new_token.insert(&mut conn).await.unwrap(), + }; MockTokenUser { app: self.app.clone(), diff --git a/src/views.rs b/src/views.rs index 239699a9dce..998342d94d1 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1,11 +1,11 @@ use chrono::NaiveDateTime; use secrecy::ExposeSecret; +use crate::controllers::token::CreatedApiToken; use crate::external_urls::remove_blocked_urls; use crate::models::{ - ApiToken, Category, Crate, CrateOwnerInvitation, CreatedApiToken, Dependency, DependencyKind, - Keyword, Owner, ReverseDependency, Team, TopVersions, User, Version, VersionDownload, - VersionOwnerAction, + ApiToken, Category, Crate, CrateOwnerInvitation, Dependency, DependencyKind, Keyword, Owner, + ReverseDependency, Team, TopVersions, User, Version, VersionDownload, VersionOwnerAction, }; use crate::util::rfc3339; use crates_io_github as github; From 3f28eb9a1dd0d52e6888d5f66e133c4354432858 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 16:09:52 +0100 Subject: [PATCH 6/7] tests/util: Remove `CreatedApiToken` dependency --- src/tests/util.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/tests/util.rs b/src/tests/util.rs index 535e77c8d22..dc7053c8d61 100644 --- a/src/tests/util.rs +++ b/src/tests/util.rs @@ -50,7 +50,6 @@ mod mock_request; mod response; mod test_app; -use crate::controllers::token::CreatedApiToken; use mock_request::MockRequest; pub use mock_request::MockRequestExt; pub use response::Response; @@ -332,14 +331,12 @@ impl MockCookieUser { .maybe_expired_at(expired_at) .build(); - let token = CreatedApiToken { - plaintext, - model: new_token.insert(&mut conn).await.unwrap(), - }; + let token = new_token.insert(&mut conn).await.unwrap(); MockTokenUser { app: self.app.clone(), token, + plaintext, } } } @@ -347,13 +344,14 @@ impl MockCookieUser { /// A type that can generate token authenticated requests pub struct MockTokenUser { app: TestApp, - token: CreatedApiToken, + token: ApiToken, + plaintext: PlainToken, } impl RequestHelper for MockTokenUser { fn request_builder(&self, method: Method, path: &str) -> MockRequest { let mut request = req(method, path); - request.header(header::AUTHORIZATION, self.token.plaintext.expose_secret()); + request.header(header::AUTHORIZATION, self.plaintext.expose_secret()); request } @@ -365,10 +363,10 @@ impl RequestHelper for MockTokenUser { impl MockTokenUser { /// Returns a reference to the database `ApiToken` model pub fn as_model(&self) -> &ApiToken { - &self.token.model + &self.token } pub fn plaintext(&self) -> &PlainToken { - &self.token.plaintext + &self.plaintext } } From 2e0bc796708c91f7c0ed962f54ba7c2b8087babd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 11 Feb 2025 16:18:02 +0100 Subject: [PATCH 7/7] controllers/token: Remove redundant `CreatedApiToken` struct --- src/controllers/token.rs | 15 ++++----------- src/views.rs | 11 ----------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/controllers/token.rs b/src/controllers/token.rs index 7cef5ebc9b6..83d80b20bd8 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -20,6 +20,7 @@ use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; use http::StatusCode; +use secrecy::ExposeSecret; #[derive(Deserialize)] pub struct GetParams { @@ -176,22 +177,14 @@ pub async fn create_api_token( } } - let created_token = CreatedApiToken { - plaintext, - model: new_token.insert(&mut conn).await?, + let api_token = EncodableApiTokenWithToken { + token: new_token.insert(&mut conn).await?, + plaintext: plaintext.expose_secret().to_string(), }; - let api_token = EncodableApiTokenWithToken::from(created_token); - Ok(json!({ "api_token": api_token })) } -#[derive(Debug)] -pub struct CreatedApiToken { - pub model: ApiToken, - pub plaintext: PlainToken, -} - /// Find API token by id. #[utoipa::path( get, diff --git a/src/views.rs b/src/views.rs index 998342d94d1..72492d664d3 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1,7 +1,5 @@ use chrono::NaiveDateTime; -use secrecy::ExposeSecret; -use crate::controllers::token::CreatedApiToken; use crate::external_urls::remove_blocked_urls; use crate::models::{ ApiToken, Category, Crate, CrateOwnerInvitation, Dependency, DependencyKind, Keyword, Owner, @@ -445,15 +443,6 @@ pub struct EncodableApiTokenWithToken { pub plaintext: String, } -impl From for EncodableApiTokenWithToken { - fn from(token: CreatedApiToken) -> Self { - EncodableApiTokenWithToken { - token: token.model, - plaintext: token.plaintext.expose_secret().to_string(), - } - } -} - #[derive(Deserialize, Serialize, Debug)] pub struct OwnedCrate { pub id: i32,