WIP - ActixWeb multi-tenant blog and newsletter API server. Originally forked from LukeMathWalker/zero-to-production.

Merge pull request #22 from DiegoEnriquezSerrano/chore/bump_dependencies_2026_02_17

Chore/bump dependencies 2026 02 17

authored by

Diego Enriquez-Serrano and committed by
GitHub
b0838cf0 bdd6a743

+116 -112
+36 -30
Cargo.lock
··· 36 36 37 37 [[package]] 38 38 name = "actix-http" 39 - version = "3.11.2" 39 + version = "3.12.0" 40 40 source = "registry+https://github.com/rust-lang/crates.io-index" 41 - checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" 41 + checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" 42 42 dependencies = [ 43 43 "actix-codec", 44 44 "actix-rt", ··· 85 85 86 86 [[package]] 87 87 name = "actix-router" 88 - version = "0.5.3" 88 + version = "0.5.4" 89 89 source = "registry+https://github.com/rust-lang/crates.io-index" 90 - checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 90 + checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" 91 91 dependencies = [ 92 92 "bytestring", 93 93 "cfg-if", ··· 165 165 166 166 [[package]] 167 167 name = "actix-web" 168 - version = "4.12.1" 168 + version = "4.13.0" 169 169 source = "registry+https://github.com/rust-lang/crates.io-index" 170 - checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" 170 + checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" 171 171 dependencies = [ 172 172 "actix-codec", 173 173 "actix-http", ··· 329 329 330 330 [[package]] 331 331 name = "anyhow" 332 - version = "1.0.100" 332 + version = "1.0.102" 333 333 source = "registry+https://github.com/rust-lang/crates.io-index" 334 - checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 334 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 335 335 336 336 [[package]] 337 337 name = "arc-swap" ··· 557 557 558 558 [[package]] 559 559 name = "bytes" 560 - version = "1.11.0" 560 + version = "1.11.1" 561 561 source = "registry+https://github.com/rust-lang/crates.io-index" 562 - checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 562 + checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 563 563 564 564 [[package]] 565 565 name = "bytestring" ··· 787 787 788 788 [[package]] 789 789 name = "cookie_store" 790 - version = "0.21.1" 790 + version = "0.22.1" 791 791 source = "registry+https://github.com/rust-lang/crates.io-index" 792 - checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" 792 + checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" 793 793 dependencies = [ 794 794 "cookie 0.18.1", 795 795 "document-features", ··· 2204 2204 2205 2205 [[package]] 2206 2206 name = "num-conv" 2207 - version = "0.1.0" 2207 + version = "0.2.0" 2208 2208 source = "registry+https://github.com/rust-lang/crates.io-index" 2209 - checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 2209 + checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" 2210 2210 2211 2211 [[package]] 2212 2212 name = "num-integer" ··· 2860 2860 2861 2861 [[package]] 2862 2862 name = "reqwest" 2863 - version = "0.12.25" 2863 + version = "0.12.28" 2864 2864 source = "registry+https://github.com/rust-lang/crates.io-index" 2865 - checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" 2865 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 2866 2866 dependencies = [ 2867 2867 "base64 0.22.1", 2868 2868 "bytes", ··· 3145 3145 3146 3146 [[package]] 3147 3147 name = "secrecy" 3148 - version = "0.8.0" 3148 + version = "0.10.3" 3149 3149 source = "registry+https://github.com/rust-lang/crates.io-index" 3150 - checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" 3150 + checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" 3151 3151 dependencies = [ 3152 3152 "serde", 3153 3153 "zeroize", ··· 3249 3249 3250 3250 [[package]] 3251 3251 name = "serde_json" 3252 - version = "1.0.145" 3252 + version = "1.0.149" 3253 3253 source = "registry+https://github.com/rust-lang/crates.io-index" 3254 - checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 3254 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 3255 3255 dependencies = [ 3256 3256 "itoa", 3257 3257 "memchr", 3258 - "ryu", 3259 3258 "serde", 3260 3259 "serde_core", 3260 + "zmij", 3261 3261 ] 3262 3262 3263 3263 [[package]] ··· 3768 3768 3769 3769 [[package]] 3770 3770 name = "time" 3771 - version = "0.3.44" 3771 + version = "0.3.47" 3772 3772 source = "registry+https://github.com/rust-lang/crates.io-index" 3773 - checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3773 + checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" 3774 3774 dependencies = [ 3775 3775 "deranged", 3776 3776 "itoa", 3777 3777 "num-conv", 3778 3778 "powerfmt", 3779 - "serde", 3779 + "serde_core", 3780 3780 "time-core", 3781 3781 "time-macros", 3782 3782 ] 3783 3783 3784 3784 [[package]] 3785 3785 name = "time-core" 3786 - version = "0.1.6" 3786 + version = "0.1.8" 3787 3787 source = "registry+https://github.com/rust-lang/crates.io-index" 3788 - checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3788 + checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" 3789 3789 3790 3790 [[package]] 3791 3791 name = "time-macros" 3792 - version = "0.2.24" 3792 + version = "0.2.27" 3793 3793 source = "registry+https://github.com/rust-lang/crates.io-index" 3794 - checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 3794 + checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" 3795 3795 dependencies = [ 3796 3796 "num-conv", 3797 3797 "time-core", ··· 3833 3833 3834 3834 [[package]] 3835 3835 name = "tokio" 3836 - version = "1.48.0" 3836 + version = "1.49.0" 3837 3837 source = "registry+https://github.com/rust-lang/crates.io-index" 3838 - checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 3838 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 3839 3839 dependencies = [ 3840 3840 "bytes", 3841 3841 "libc", ··· 4943 4943 version = "0.5.5" 4944 4944 source = "registry+https://github.com/rust-lang/crates.io-index" 4945 4945 checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" 4946 + 4947 + [[package]] 4948 + name = "zmij" 4949 + version = "1.0.21" 4950 + source = "registry+https://github.com/rust-lang/crates.io-index" 4951 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" 4946 4952 4947 4953 [[package]] 4948 4954 name = "zstd"
+6 -6
Cargo.toml
··· 23 23 [dependencies] 24 24 actix-cors = "0.7.1" 25 25 actix-session = { version = "0.11.0", features = ["redis-session-rustls"] } 26 - actix-web = "4.12.1" 26 + actix-web = "4.13.0" 27 27 actix-web-flash-messages = { version = "0.5.0", features = ["cookies"] } 28 28 aes-gcm = "0.10.3" 29 - anyhow = "1.0.100" 29 + anyhow = "1.0.102" 30 30 argon2 = { version = "0.5.3", features = ["std"] } 31 31 base64 = "0.22.1" 32 32 captcha = "1.0.0" ··· 38 38 regex = "1.11.3" 39 39 rpassword = "7.4" 40 40 rust-s3 = { version = "0.37.0", features = ["tokio"] } 41 - secrecy = { version = "0.8", features = ["serde"] } 41 + secrecy = { version = "0.10.3", features = ["serde"] } 42 42 serde = { version = "1.0.226", features = ["derive"] } 43 - serde_json = "1.0.145" 43 + serde_json = "1.0.149" 44 44 serde-aux = "4.7.0" 45 45 sha1 = "0.10.6" 46 46 slug = "0.1.6" 47 47 tera = "1.20.1" 48 48 thiserror = "1.0.24" 49 - tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } 49 + tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } 50 50 tracing = "0.1.41" 51 51 tracing-actix-web = "0.7.19" 52 52 tracing-bunyan-formatter = "0.3.10" ··· 65 65 [dependencies.reqwest] 66 66 default-features = false 67 67 features = ["cookies", "json", "rustls-tls"] 68 - version = "0.12.25" 68 + version = "0.12.28" 69 69 70 70 [dependencies.sqlx] 71 71 default-features = false
+12 -11
src/authentication/password.rs
··· 2 2 use anyhow::Context; 3 3 use argon2::password_hash::SaltString; 4 4 use argon2::{Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version}; 5 - use secrecy::{ExposeSecret, Secret}; 5 + use secrecy::{ExposeSecret, SecretString}; 6 6 use sqlx::PgPool; 7 7 8 8 #[derive(thiserror::Error, Debug)] ··· 17 17 18 18 pub struct Credentials { 19 19 pub username: String, 20 - pub password: Secret<String>, 20 + pub password: SecretString, 21 21 } 22 22 23 23 #[tracing::instrument(name = "Get stored credentials", skip(username, pool))] 24 24 async fn get_stored_credentials( 25 25 username: &str, 26 26 pool: &PgPool, 27 - ) -> Result<Option<(uuid::Uuid, Secret<String>)>, anyhow::Error> { 27 + ) -> Result<Option<(uuid::Uuid, SecretString)>, anyhow::Error> { 28 28 let row = sqlx::query!( 29 29 r#" 30 30 SELECT user_id, password_hash ··· 36 36 .fetch_optional(pool) 37 37 .await 38 38 .context("Failed to performed a query to retrieve stored credentials.")? 39 - .map(|row| (row.user_id, Secret::new(row.password_hash))); 39 + .map(|row| (row.user_id, SecretString::new(row.password_hash.into()))); 40 40 Ok(row) 41 41 } 42 42 ··· 46 46 pool: &PgPool, 47 47 ) -> Result<uuid::Uuid, AuthError> { 48 48 let mut user_id = None; 49 - let mut expected_password_hash = Secret::new( 49 + let mut expected_password_hash = SecretString::new( 50 50 "$argon2id$v=19$m=15000,t=2,p=1$\ 51 51 gZiV/M1gPc22ElAH/Jh1Hw$\ 52 52 CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno" 53 - .to_string(), 53 + .to_string() 54 + .into(), 54 55 ); 55 56 56 57 if let Some((stored_user_id, stored_password_hash)) = ··· 76 77 skip(expected_password_hash, password_candidate) 77 78 )] 78 79 fn verify_password_hash( 79 - expected_password_hash: Secret<String>, 80 - password_candidate: Secret<String>, 80 + expected_password_hash: SecretString, 81 + password_candidate: SecretString, 81 82 ) -> Result<(), AuthError> { 82 83 let expected_password_hash = PasswordHash::new(expected_password_hash.expose_secret()) 83 84 .context("Failed to parse hash in PHC string format.")?; ··· 94 95 #[tracing::instrument(name = "Change password", skip(password, pool))] 95 96 pub async fn change_password( 96 97 user_id: uuid::Uuid, 97 - password: Secret<String>, 98 + password: SecretString, 98 99 pool: &PgPool, 99 100 ) -> Result<(), anyhow::Error> { 100 101 let password_hash = spawn_blocking_with_tracing(move || compute_password_hash(password)) ··· 115 116 Ok(()) 116 117 } 117 118 118 - fn compute_password_hash(password: Secret<String>) -> Result<Secret<String>, anyhow::Error> { 119 + fn compute_password_hash(password: SecretString) -> Result<SecretString, anyhow::Error> { 119 120 let salt = SaltString::generate(&mut rand::thread_rng()); 120 121 let password_hash = Argon2::new( 121 122 Algorithm::Argon2id, ··· 124 125 ) 125 126 .hash_password(password.expose_secret().as_bytes(), &salt)? 126 127 .to_string(); 127 - Ok(Secret::new(password_hash)) 128 + Ok(SecretString::new(password_hash.into())) 128 129 }
+12 -12
src/challenge/base64_challenger.rs
··· 5 5 use base64::engine::general_purpose::STANDARD; 6 6 use captcha::Captcha; 7 7 use captcha::filters::{Dots, Grid, Noise, Wave}; 8 - use secrecy::{ExposeSecret, Secret}; 8 + use secrecy::{ExposeSecret, SecretString}; 9 9 use serde::{Deserialize, Serialize}; 10 10 11 11 #[derive(Deserialize, Debug)] 12 12 pub struct Base64Challenger { 13 13 pub base64_image: String, 14 14 answer: String, 15 - secret: Secret<String>, 15 + secret: SecretString, 16 16 } 17 17 18 18 impl Base64Challenger { 19 - pub fn new(secret: Secret<String>) -> Result<Self, anyhow::Error> { 19 + pub fn new(secret: SecretString) -> Result<Self, anyhow::Error> { 20 20 if secret.expose_secret().len() != 32 { 21 21 anyhow::bail!("Secret must be 32 bytes in length.") 22 22 } ··· 61 61 Ok(STANDARD.encode(out)) 62 62 } 63 63 64 - pub fn decrypt(encoded: &str, secret: Secret<String>) -> Result<String, anyhow::Error> { 64 + pub fn decrypt(encoded: &str, secret: SecretString) -> Result<String, anyhow::Error> { 65 65 let data = STANDARD 66 66 .decode(encoded) 67 67 .context("Failed to base64 decode challenge.")?; ··· 87 87 pub fn verify( 88 88 encoded: &str, 89 89 answer: String, 90 - secret: Secret<String>, 90 + secret: SecretString, 91 91 ) -> Result<(), anyhow::Error> { 92 92 if Self::decrypt(encoded, secret)? == answer { 93 93 Ok(()) ··· 107 107 mod tests { 108 108 use crate::challenge::Base64Challenger; 109 109 use claims::{assert_err, assert_ok}; 110 - use secrecy::Secret; 110 + use secrecy::SecretString; 111 111 112 112 #[test] 113 113 fn secret_must_be_32_bytes_in_length() { 114 - let secret = Secret::from("W81lMp7E1J0569L2Z1ERpeX8XDiYn11".to_string()); 114 + let secret = SecretString::from("W81lMp7E1J0569L2Z1ERpeX8XDiYn11"); 115 115 let challenge = Base64Challenger::new(secret); 116 116 117 117 assert_err!(challenge); ··· 119 119 120 120 #[test] 121 121 fn can_create_challenge_image() { 122 - let secret = Secret::from("w8ar9i496zulwEayDG828Y67i09IfwWC".to_string()); 122 + let secret = SecretString::from("w8ar9i496zulwEayDG828Y67i09IfwWC"); 123 123 let challenge = Base64Challenger::new(secret); 124 124 125 125 assert_ok!(challenge); ··· 127 127 128 128 #[test] 129 129 fn can_encrypt_challenge() { 130 - let secret = Secret::from("njE17BV5QLYO82V3UWoa22ZwwdiD40l2".to_string()); 130 + let secret = SecretString::from("njE17BV5QLYO82V3UWoa22ZwwdiD40l2"); 131 131 let challenge = Base64Challenger::new(secret).expect("Creating challenge."); 132 132 133 133 assert_ok!(challenge.encrypt()); ··· 135 135 136 136 #[test] 137 137 fn can_decrypt_challenge() { 138 - let secret = Secret::from("tNuS550e9os25IFZxw518GlNSK3ouiY1".to_string()); 138 + let secret = SecretString::from("tNuS550e9os25IFZxw518GlNSK3ouiY1"); 139 139 let challenge = Base64Challenger::new(secret).expect("Creating challenge."); 140 140 let encrypted = challenge.encrypt().unwrap(); 141 141 let decrypted = Base64Challenger::decrypt(&encrypted, challenge.secret).unwrap(); ··· 145 145 146 146 #[test] 147 147 fn can_verify_correct_answer() { 148 - let secret = Secret::from("7LphV05vqV3oxYj831j97H3vs2g5wP89".to_string()); 148 + let secret = SecretString::from("7LphV05vqV3oxYj831j97H3vs2g5wP89"); 149 149 let challenge = Base64Challenger::new(secret).expect("Creating challenge."); 150 150 let encrypted = challenge.encrypt().unwrap(); 151 151 ··· 158 158 159 159 #[test] 160 160 fn can_reject_incorrect_answer() { 161 - let secret = Secret::from("Zh20YpU56L5Ces0VffGl31rb2Km4k7Gr".to_string()); 161 + let secret = SecretString::from("Zh20YpU56L5Ces0VffGl31rb2Km4k7Gr"); 162 162 let challenge = Base64Challenger::new(secret).expect("Creating challenge."); 163 163 let encrypted = challenge.encrypt().unwrap(); 164 164
+5 -5
src/clients/cloudinary_client.rs
··· 2 2 use actix_web::web; 3 3 use anyhow::Context; 4 4 use reqwest::Client; 5 - use secrecy::{ExposeSecret, Secret}; 5 + use secrecy::{ExposeSecret, SecretString}; 6 6 use serde::{Deserialize, Serialize}; 7 7 use sha1::{Digest, Sha1}; 8 8 use std::fmt::Display; ··· 12 12 13 13 pub struct CloudinaryClient { 14 14 pub api_key: String, 15 - pub api_secret: Secret<String>, 15 + pub api_secret: SecretString, 16 16 pub base_url: String, 17 17 pub bucket: String, 18 18 pub http_client: Client, ··· 21 21 impl CloudinaryClient { 22 22 pub fn new( 23 23 api_key: String, 24 - api_secret: Secret<String>, 24 + api_secret: SecretString, 25 25 base_url: String, 26 26 bucket: String, 27 27 timeout: std::time::Duration, ··· 235 235 use claims::{assert_err, assert_ok}; 236 236 use fake::faker::lorem::en::Word; 237 237 use fake::{Fake, Faker}; 238 - use secrecy::Secret; 238 + use secrecy::SecretString; 239 239 use wiremock::matchers::{any, method, path}; 240 240 use wiremock::{Mock, MockServer, Request, ResponseTemplate}; 241 241 ··· 284 284 fn cloudinary_client(base_url: String) -> CloudinaryClient { 285 285 CloudinaryClient::new( 286 286 Faker.fake(), 287 - Secret::new(Faker.fake()), 287 + SecretString::from(Faker.fake::<String>()), 288 288 base_url, 289 289 bucket(), 290 290 std::time::Duration::from_millis(200),
+7 -7
src/configuration.rs
··· 2 2 use crate::clients::s3_client::S3Client; 3 3 use crate::domain::SubscriberEmail; 4 4 use crate::email_client::{EmailClient, EmailServer, deserialize_email_server_from_string}; 5 - use secrecy::{ExposeSecret, Secret}; 5 + use secrecy::{ExposeSecret, SecretString}; 6 6 use serde::Deserialize; 7 7 use serde_aux::field_attributes::deserialize_number_from_string; 8 8 use sqlx::postgres::{PgConnectOptions, PgSslMode}; ··· 16 16 pub email_client: EmailClientSettings, 17 17 pub s3_client: S3ClientSettings, 18 18 pub hosts: HostnameSettings, 19 - pub redis_uri: Secret<String>, 19 + pub redis_uri: SecretString, 20 20 } 21 21 22 22 #[derive(Deserialize, Clone)] 23 23 pub struct ApplicationSettings { 24 24 pub base_url: String, 25 - pub captcha_secret: Secret<String>, 26 - pub hmac_secret: Secret<String>, 25 + pub captcha_secret: SecretString, 26 + pub hmac_secret: SecretString, 27 27 pub host: String, 28 28 #[serde(deserialize_with = "deserialize_number_from_string")] 29 29 pub port: u16, ··· 33 33 #[derive(Deserialize, Clone)] 34 34 pub struct CloudinaryClientSettings { 35 35 pub api_key: String, 36 - pub api_secret: Secret<String>, 36 + pub api_secret: SecretString, 37 37 pub base_url: String, 38 38 pub bucket: String, 39 39 pub id: String, ··· 77 77 pub struct DatabaseSettings { 78 78 pub database_name: String, 79 79 pub host: String, 80 - pub password: Secret<String>, 80 + pub password: SecretString, 81 81 #[serde(deserialize_with = "deserialize_number_from_string")] 82 82 pub port: u16, 83 83 pub require_ssl: bool, ··· 105 105 pub struct EmailClientSettings { 106 106 pub base_url: String, 107 107 pub sender_email: String, 108 - pub authorization_token: Secret<String>, 108 + pub authorization_token: SecretString, 109 109 #[serde(deserialize_with = "deserialize_number_from_string")] 110 110 pub timeout_milliseconds: u64, 111 111 #[serde(deserialize_with = "deserialize_email_server_from_string")]
+5 -5
src/email_client.rs
··· 1 1 use crate::domain::SubscriberEmail; 2 2 use reqwest::Client; 3 - use secrecy::{ExposeSecret, Secret}; 3 + use secrecy::{ExposeSecret, SecretString}; 4 4 use serde::{Deserialize, Deserializer, Serialize}; 5 5 use tera::{Context, Tera}; 6 6 ··· 8 8 http_client: Client, 9 9 base_url: String, 10 10 sender: SubscriberEmail, 11 - authorization_token: Secret<String>, 11 + authorization_token: SecretString, 12 12 pub server: EmailServer, 13 13 } 14 14 ··· 16 16 pub fn new( 17 17 base_url: String, 18 18 sender: SubscriberEmail, 19 - authorization_token: Secret<String>, 19 + authorization_token: SecretString, 20 20 timeout: std::time::Duration, 21 21 server: EmailServer, 22 22 ) -> Self { ··· 173 173 use fake::faker::internet::en::SafeEmail; 174 174 use fake::faker::lorem::en::{Paragraph, Sentence}; 175 175 use fake::{Fake, Faker}; 176 - use secrecy::Secret; 176 + use secrecy::SecretString; 177 177 use wiremock::matchers::{any, header, header_exists, method, path}; 178 178 use wiremock::{Mock, MockServer, Request, ResponseTemplate}; 179 179 ··· 214 214 EmailClient::new( 215 215 base_url, 216 216 email(), 217 - Secret::new(Faker.fake()), 217 + SecretString::from(Faker.fake::<String>()), 218 218 std::time::Duration::from_millis(200), 219 219 EmailServer::Postmark, 220 220 )
+5 -5
src/models/user.rs
··· 2 2 use anyhow::Context; 3 3 use argon2::password_hash::SaltString; 4 4 use argon2::{Algorithm, Argon2, Params, PasswordHasher, Version}; 5 - use secrecy::{ExposeSecret, Secret}; 5 + use secrecy::{ExposeSecret, SecretString}; 6 6 use serde::{Deserialize, Serialize}; 7 7 use sqlx::postgres::PgRow; 8 8 use sqlx::{Executor, Postgres, Row, Transaction}; ··· 109 109 pub struct NewUserData { 110 110 pub email: String, 111 111 pub username: String, 112 - pub password: Secret<String>, 112 + pub password: SecretString, 113 113 } 114 114 115 115 fn prepare_new_user(data: NewUserData) -> Result<NewUser, String> { ··· 139 139 mod tests { 140 140 use crate::models::{NewUser, NewUserData}; 141 141 use claims::{assert_err, assert_ok}; 142 - use secrecy::Secret; 142 + use secrecy::SecretString; 143 143 144 144 #[test] 145 145 fn valid_new_user_data_can_convert_into_user() { 146 146 let test_user = NewUserData { 147 147 email: "myguy@example.com".to_string(), 148 - password: Secret::new("secretpassword".to_string()), 148 + password: SecretString::new("secretpassword".to_string().into()), 149 149 username: "Ursula-Le-Guin".to_string(), 150 150 }; 151 151 ··· 156 156 fn invalid_new_user_data_cannot_convert_into_user() { 157 157 let test_user = NewUserData { 158 158 email: "myguy@example.com".to_string(), 159 - password: Secret::new("secretpassword".to_string()), 159 + password: SecretString::new("secretpassword".to_string().into()), 160 160 username: "Ursula Le Guin".to_string(), 161 161 }; 162 162
+5 -8
src/routes/admin/password.rs
··· 2 2 use crate::routes::admin::dashboard::get_username; 3 3 use crate::utils::{e400, e500}; 4 4 use actix_web::{HttpResponse, put, web}; 5 - use secrecy::{ExposeSecret, Secret}; 5 + use secrecy::{ExposeSecret, SecretString}; 6 6 use serde::Deserialize; 7 7 use sqlx::PgPool; 8 8 9 9 #[derive(Deserialize, Debug)] 10 10 pub struct ChangePasswordParams { 11 - current_password: Secret<String>, 12 - new_password: Secret<String>, 13 - new_password_check: Secret<String>, 11 + current_password: SecretString, 12 + new_password: SecretString, 13 + new_password_check: SecretString, 14 14 } 15 15 16 16 #[put("/password")] ··· 42 42 Ok(HttpResponse::Ok().finish()) 43 43 } 44 44 45 - fn validate_password( 46 - password: &Secret<String>, 47 - password_check: &Secret<String>, 48 - ) -> Result<(), String> { 45 + fn validate_password(password: &SecretString, password_check: &SecretString) -> Result<(), String> { 49 46 if password.expose_secret() != password_check.expose_secret() { 50 47 Err(ValidationFailure::Mismatch.into()) 51 48 } else if password.expose_secret().len() <= 12 {
+2 -2
src/routes/login.rs
··· 5 5 use actix_web::http::header::ContentType; 6 6 use actix_web::{HttpResponse, post, web}; 7 7 use actix_web_flash_messages::FlashMessage; 8 - use secrecy::Secret; 8 + use secrecy::SecretString; 9 9 use serde::Deserialize; 10 10 use sqlx::PgPool; 11 11 12 12 #[derive(Deserialize, Debug)] 13 13 pub struct LoginParams { 14 14 username: String, 15 - password: Secret<String>, 15 + password: SecretString, 16 16 } 17 17 18 18 #[post("/login")]
+2 -2
src/routes/subscriptions/index.rs
··· 6 6 use crate::utils::{e400, e404, e500}; 7 7 use actix_web::{HttpResponse, post, web}; 8 8 use anyhow::Context; 9 - use secrecy::Secret; 9 + use secrecy::SecretString; 10 10 use serde::{Deserialize, Serialize}; 11 11 use sqlx::{PgPool, Postgres, Transaction}; 12 12 ··· 22 22 impl NewSubscriber { 23 23 async fn try_from( 24 24 params: SubscribeParams, 25 - captcha_secret: Secret<String>, 25 + captcha_secret: SecretString, 26 26 transaction: &mut Transaction<'_, Postgres>, 27 27 ) -> Result<Self, actix_web::Error> { 28 28 let decrypted =
+6 -6
src/startup.rs
··· 16 16 use actix_web::{App, HttpServer, web}; 17 17 use actix_web_flash_messages::FlashMessagesFramework; 18 18 use actix_web_flash_messages::storage::CookieMessageStore; 19 - use secrecy::{ExposeSecret, Secret}; 19 + use secrecy::{ExposeSecret, SecretString}; 20 20 use sqlx::PgPool; 21 21 use sqlx::postgres::PgPoolOptions; 22 22 use std::net::TcpListener; ··· 80 80 email_client: EmailClient, 81 81 s3_client: S3Client, 82 82 base_url: String, 83 - hmac_secret: Secret<String>, 84 - redis_uri: Secret<String>, 83 + hmac_secret: SecretString, 84 + redis_uri: SecretString, 85 85 host_origin_url: String, 86 86 client_url: String, 87 87 session_key: String, 88 - captcha_secret: Secret<String>, 88 + captcha_secret: SecretString, 89 89 ) -> Result<Server, anyhow::Error> { 90 90 let base_url = Data::new(ApplicationBaseUrl(base_url)); 91 91 let client_base_url = Data::new(ApplicationClientBaseUrl(client_url.clone())); ··· 178 178 pub struct ApplicationClientBaseUrl(pub String); 179 179 180 180 #[derive(Clone)] 181 - pub struct HmacSecret(pub Secret<String>); 181 + pub struct HmacSecret(pub SecretString); 182 182 183 183 #[derive(Clone)] 184 - pub struct CaptchaSecret(pub Secret<String>); 184 + pub struct CaptchaSecret(pub SecretString);
+2 -2
src/superuser.rs
··· 2 2 use newsletter_api::configuration::get_configuration; 3 3 use newsletter_api::models::{NewUser, NewUserData, UserProfile}; 4 4 use newsletter_api::startup::get_connection_pool; 5 - use secrecy::Secret; 5 + use secrecy::SecretString; 6 6 use std::io::{self, Write}; 7 7 8 8 #[tokio::main] ··· 40 40 let new_user = NewUserData { 41 41 email, 42 42 username, 43 - password: Secret::new(password_check), 43 + password: SecretString::new(password_check.into()), 44 44 }; 45 45 let mut transaction = connection_pool 46 46 .begin()
+3 -3
tests/api/admin/newsletters/detail/index.rs
··· 1 1 use crate::helpers::spawn_app; 2 2 use newsletter_api::models::{NewUser, NewUserData, NewsletterIssueAPI}; 3 3 use newsletter_api::utils::ResponseErrorMessage; 4 - use secrecy::Secret; 4 + use secrecy::SecretString; 5 5 6 6 #[tokio::test] 7 7 async fn unauthenticated_user_cannot_fetch_a_newsletter() { ··· 71 71 let second_user: NewUser = NewUserData { 72 72 username: uuid::Uuid::new_v4().to_string(), 73 73 email: String::from("seconduser@example.org"), 74 - password: Secret::new(String::from("testpassword")), 74 + password: SecretString::from("testpassword"), 75 75 } 76 76 .try_into() 77 77 .unwrap(); ··· 435 435 let second_user: NewUser = NewUserData { 436 436 username: uuid::Uuid::new_v4().to_string(), 437 437 email: String::from("seconduser@example.org"), 438 - password: Secret::new(String::from("testpassword")), 438 + password: SecretString::from("testpassword"), 439 439 } 440 440 .try_into() 441 441 .unwrap();
+2 -2
tests/api/admin/newsletters/detail/publish.rs
··· 3 3 use fake::faker::internet::en::SafeEmail; 4 4 use newsletter_api::models::{NewUser, NewUserData, NewsletterIssueAPI}; 5 5 use newsletter_api::utils::{ResponseErrorMessage, ResponseMessage}; 6 - use secrecy::Secret; 6 + use secrecy::SecretString; 7 7 use std::time::Duration; 8 8 use wiremock::matchers::{any, method, path}; 9 9 use wiremock::{Mock, ResponseTemplate}; ··· 263 263 let second_user: NewUser = NewUserData { 264 264 username: uuid::Uuid::new_v4().to_string(), 265 265 email: SafeEmail().fake(), 266 - password: Secret::from(uuid::Uuid::new_v4().to_string()), 266 + password: SecretString::from(uuid::Uuid::new_v4().to_string()), 267 267 } 268 268 .try_into() 269 269 .unwrap();
+4 -4
tests/api/helpers.rs
··· 9 9 use newsletter_api::models::{NewUser, NewUserData, UserProfile}; 10 10 use newsletter_api::startup::{Application, get_connection_pool}; 11 11 use newsletter_api::telemetry::{get_subscriber, init_subscriber}; 12 - use secrecy::Secret; 12 + use secrecy::SecretString; 13 13 use sqlx::{Connection, Executor, PgConnection, PgPool}; 14 14 use std::sync::LazyLock; 15 15 use uuid::Uuid; ··· 39 39 pub cloudinary_client: CloudinaryClient, 40 40 pub cloudinary_server: MockServer, 41 41 pub email_client: EmailClient, 42 - pub captcha_secret: Secret<String>, 42 + pub captcha_secret: SecretString, 43 43 } 44 44 45 45 /// Confirmation links embedded in the request to the email API. ··· 469 469 let maintenance_settings = DatabaseSettings { 470 470 database_name: "postgres".to_string(), 471 471 username: "postgres".to_string(), 472 - password: Secret::new("password".to_string()), 472 + password: SecretString::from("password".to_string()), 473 473 ..config.clone() 474 474 }; 475 475 let mut connection = PgConnection::connect_with(&maintenance_settings.connect_options()) ··· 502 502 let password: String = Uuid::new_v4().to_string(); 503 503 let new_user: NewUser = NewUserData { 504 504 username: Uuid::new_v4().to_string(), 505 - password: Secret::from(password.clone()), 505 + password: SecretString::from(password.clone()), 506 506 email: SafeEmail().fake(), 507 507 } 508 508 .try_into()
+2 -2
tests/api/newsletters/index.rs
··· 4 4 use newsletter_api::models::{ 5 5 NewUser, NewUserData, NewsletterIssueAPI, PublicNewsletterListItem, UserProfile, 6 6 }; 7 - use secrecy::Secret; 7 + use secrecy::SecretString; 8 8 9 9 #[tokio::test] 10 10 async fn unauthenticated_user_can_get_published_timeline_newsletter_issues() { ··· 116 116 let second_user = NewUser::try_from(NewUserData { 117 117 username: uuid::Uuid::new_v4().to_string(), 118 118 email: SafeEmail().fake(), 119 - password: Secret::from("testpassword".to_string()), 119 + password: SecretString::from("testpassword"), 120 120 }) 121 121 .expect("Failed to initialize new user.") 122 122 .store(&mut transaction)