Repository: sebadob/rauthy (language: rust)

Issue:
Create manual `impl Debug` for Principal / Session / ApiKey
To never even accidentially leak any sensitive information into the logs with `LOG_LEVEL=debug`, create a manual `impl` for `Debug` + `Display` for Principal / Session / ApiKey / AuthCode / DeviceCode.


Discussion / hints:

Fix patch:
diff --git a/src/models/src/entity/api_keys.rs b/src/models/src/entity/api_keys.rs
index e304a8ca4..42b0833af 100644
--- a/src/models/src/entity/api_keys.rs
+++ b/src/models/src/entity/api_keys.rs
@@ -10,8 +10,9 @@ use rauthy_error::{ErrorResponse, ErrorResponseType};
 use ring::digest;
 use serde::{Deserialize, Serialize};
 use sqlx::{query, query_as, FromRow};
+use std::fmt::{Debug, Formatter};
 
-#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
+#[derive(Clone, Serialize, Deserialize, FromRow)]
 pub struct ApiKeyEntity {
     pub name: String,
     pub secret: Vec<u8>,
@@ -21,6 +22,16 @@ pub struct ApiKeyEntity {
     pub access: Vec<u8>,
 }
 
+impl Debug for ApiKeyEntity {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "name: {}, secret: <hidden>, created: {}, expires: {:?}, enc_key_id: {}, access: {:?}",
+            self.name, self.created, self.expires, self.enc_key_id, self.access
+        )
+    }
+}
+
 impl ApiKeyEntity {
     pub async fn create(
         name: String,
@@ -338,7 +349,7 @@ pub struct ApiKeyAccess {
     pub access_rights: Vec<AccessRights>,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
 pub struct ApiKey {
     pub name: String,
     /// SHA256 hashed secret key
@@ -348,6 +359,16 @@ pub struct ApiKey {
     pub access: Vec<ApiKeyAccess>,
 }
 
+impl Debug for ApiKey {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "name: {}, secret: <hidden>, created: {}, expires: {:?}, access: {:?}",
+            self.name, self.created, self.expires, self.access
+        )
+    }
+}
+
 impl ApiKey {
     #[inline(always)]
     pub fn validate_access(
diff --git a/src/models/src/entity/auth_codes.rs b/src/models/src/entity/auth_codes.rs
index 6dda1c86a..c9318e30a 100644
--- a/src/models/src/entity/auth_codes.rs
+++ b/src/models/src/entity/auth_codes.rs
@@ -3,10 +3,11 @@ use rauthy_common::constants::CACHE_TTL_AUTH_CODE;
 use rauthy_common::utils::get_rand;
 use rauthy_error::ErrorResponse;
 use serde::{Deserialize, Serialize};
+use std::fmt::{Debug, Formatter};
 use std::ops::Add;
 use time::OffsetDateTime;
 
-#[derive(Debug, Deserialize, Serialize)]
+#[derive(Deserialize, Serialize)]
 pub struct AuthCode {
     pub id: String,
     pub exp: i64,
@@ -19,6 +20,20 @@ pub struct AuthCode {
     pub scopes: Vec<String>,
 }
 
+impl Debug for AuthCode {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}(...), exp: {}, client_id: {}, user_id: {}, scopes: {:?}",
+            &self.id[..5],
+            self.exp,
+            self.client_id,
+            self.user_id,
+            self.scopes
+        )
+    }
+}
+
 // CRUD
 impl AuthCode {
     // Deletes an Authorization Code from the cache
diff --git a/src/models/src/entity/clients.rs b/src/models/src/entity/clients.rs
index da39e01fb..13e512d83 100644
--- a/src/models/src/entity/clients.rs
+++ b/src/models/src/entity/clients.rs
@@ -28,6 +28,7 @@ use reqwest::header::CONTENT_TYPE;
 use reqwest::{tls, Url};
 use serde::{Deserialize, Serialize};
 use sqlx::{FromRow, Row};
+use std::fmt::{Debug, Formatter};
 use std::str::FromStr;
 use std::sync::OnceLock;
 use std::time::Duration;
@@ -46,7 +47,7 @@ performance, when we do reads on clients, which we do most of the time.
 
 `*_lifetime` values are meant to be in seconds.
  */
-#[derive(Debug, Clone, PartialEq, Eq, FromRow, Deserialize, Serialize)]
+#[derive(Clone, PartialEq, Eq, FromRow, Deserialize, Serialize)]
 pub struct Client {
     pub id: String,
     pub name: Option<String>,
@@ -72,6 +73,37 @@ pub struct Client {
     pub contacts: Option<String>,
 }
 
+impl Debug for Client {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}, name: {:?}, endabled: {}, confidential: {}, secret: <hidden>, \
+        redirect_uris: {}, post_logout_redirect_uris: {:?}, allowed_origins: {:?}, \
+        flows_enabled: {}, access_token_alg: {}, id_token_alg: {}, auth_code_lifetime: {}, \
+        access_token_lifetime: {}, scopes: {}, default_scopes: {}, challenge: {:?}, force_mfa: {}, \
+        client_uri: {:?}, contacts: {:?}",
+            self.id,
+            self.name,
+            self.enabled,
+            self.confidential,
+            self.redirect_uris,
+            self.post_logout_redirect_uris,
+            self.allowed_origins,
+            self.flows_enabled,
+            self.access_token_alg,
+            self.id_token_alg,
+            self.auth_code_lifetime,
+            self.access_token_lifetime,
+            self.scopes,
+            self.default_scopes,
+            self.challenge,
+            self.force_mfa,
+            self.client_uri,
+            self.contacts
+        )
+    }
+}
+
 // CRUD
 impl Client {
     #[inline]
@@ -184,7 +216,7 @@ post_logout_redirect_uris, allowed_origins, flows_enabled, access_token_alg, id_
 auth_code_lifetime, access_token_lifetime, scopes, default_scopes, challenge, force_mfa,
 client_uri, contacts)
 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)"#,
-                params!(
+                 params!(
                     &client.id,
                     &client.name,
                     client.enabled,
@@ -210,7 +242,7 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $
 INSERT INTO
 clients_dyn (id, created, registration_token, token_endpoint_auth_method)
 VALUES ($1, $2, $3, $4)"#,
-                params!(
+                 params!(
                     client.id.clone(),
                     created,
                     registration_token.clone(),
@@ -248,8 +280,8 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $
                 client.client_uri,
                 client.contacts,
             )
-            .execute(&mut *txn)
-            .await?;
+                .execute(&mut *txn)
+                .await?;
 
             sqlx::query!(
                 r#"
@@ -1776,11 +1808,12 @@ mod tests {
                         }),
                     )
                 })
-                .bind(("127.0.0.1", 10080))
-                .expect("port 10080 to be free for testing")
-                .run()
-                .await
-                .expect("ephemeral client test http server to start") })
+                    .bind(("127.0.0.1", 10080))
+                    .expect("port 10080 to be free for testing")
+                    .run()
+                    .await
+                    .expect("ephemeral client test http server to start")
+            })
         })
     }
 
diff --git a/src/models/src/entity/dpop_proof.rs b/src/models/src/entity/dpop_proof.rs
index 001cc40ba..7edd5dd44 100644
--- a/src/models/src/entity/dpop_proof.rs
+++ b/src/models/src/entity/dpop_proof.rs
@@ -10,16 +10,23 @@ use rauthy_common::constants::{
 use rauthy_common::utils::{base64_url_no_pad_decode, get_rand};
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
+use std::fmt::{Debug, Formatter};
 use std::ops::{Add, Sub};
 use tracing::error;
 
 /// A DPoP nonce that only live inside the cache to limit client's DPoP lifetimes
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Serialize, Deserialize)]
 pub struct DPoPNonce {
     pub exp: DateTime<Utc>,
     pub value: String,
 }
 
+impl Debug for DPoPNonce {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "exp: {}, value: <hidden>", self.exp)
+    }
+}
+
 impl DPoPNonce {
     /// Creates a new DPoP nonce, inserts it into the cache and returns its value.
     pub async fn new_value() -> Result<String, ErrorResponse> {
diff --git a/src/models/src/entity/jwk.rs b/src/models/src/entity/jwk.rs
index 4f72a5fc4..318c99e15 100644
--- a/src/models/src/entity/jwk.rs
+++ b/src/models/src/entity/jwk.rs
@@ -98,7 +98,7 @@ macro_rules! validate_jwt {
 The Json Web Keys are saved encrypted inside the database. The encryption is the same as for a
 Client secret -> *ChaCha20Poly1305*
  */
-#[derive(Debug, FromRow, Serialize, Deserialize)]
+#[derive(FromRow, Serialize, Deserialize)]
 pub struct Jwk {
     pub kid: String,
     pub created_at: i64,
@@ -108,6 +108,16 @@ pub struct Jwk {
     pub jwk: Vec<u8>,
 }
 
+impl Debug for Jwk {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "kid: {}, created_at: {}, signature: {:?}, enc_key_id: {}, jwk: <hidden>",
+            self.kid, self.created_at, self.signature, self.enc_key_id,
+        )
+    }
+}
+
 // CRUD
 impl Jwk {
     pub async fn save(&self) -> Result<(), ErrorResponse> {
@@ -599,13 +609,19 @@ impl From<JWKSPublicKey> for JWKSPublicKeyCerts {
     }
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
 pub struct JwkKeyPair {
     pub kid: String,
     pub typ: JwkKeyPairAlg,
     pub bytes: Vec<u8>,
 }
 
+impl Debug for JwkKeyPair {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "kid: {}, typ: {}, bytes: <hidden>", self.kid, self.typ)
+    }
+}
+
 impl JwkKeyPair {
     // Decrypts a Json Web Key which is in an encrypted format from inside the database
     pub fn decrypt(jwk_entity: &Jwk, key_pair_type: JwkKeyPairAlg) -> Result<Self, ErrorResponse> {
diff --git a/src/models/src/entity/magic_links.rs b/src/models/src/entity/magic_links.rs
index f74d605b2..ebb03d892 100644
--- a/src/models/src/entity/magic_links.rs
+++ b/src/models/src/entity/magic_links.rs
@@ -8,7 +8,7 @@ use rauthy_common::utils::{get_rand, real_ip_from_req};
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
-use std::fmt::{Display, Formatter};
+use std::fmt::{Debug, Display, Formatter};
 use time::OffsetDateTime;
 use tracing::warn;
 
@@ -85,7 +85,7 @@ impl Display for MagicLinkUsage {
     }
 }
 
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
+#[derive(Clone, FromRow, Serialize, Deserialize)]
 pub struct MagicLink {
     pub id: String,
     pub user_id: String,
@@ -96,6 +96,20 @@ pub struct MagicLink {
     pub usage: String,
 }
 
+impl Debug for MagicLink {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "id: {}(...), user_id: {}, csrf_token: {}(...), cookie: {:?}, exp: {}, used: {}, usage: {}",
+               &self.id[..5],
+               self.user_id,
+               &self.csrf_token[..5],
+               self.cookie,
+               self.exp,
+               self.used,
+               self.usage
+        )
+    }
+}
+
 // CRUD
 impl MagicLink {
     pub async fn create(
diff --git a/src/models/src/entity/refresh_tokens.rs b/src/models/src/entity/refresh_tokens.rs
index c462551bb..eff837f07 100644
--- a/src/models/src/entity/refresh_tokens.rs
+++ b/src/models/src/entity/refresh_tokens.rs
@@ -5,8 +5,9 @@ use rauthy_common::is_hiqlite;
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
+use std::fmt::{Debug, Formatter};
 
-#[derive(Debug, FromRow, Serialize, Deserialize)]
+#[derive(FromRow, Serialize, Deserialize)]
 pub struct RefreshToken {
     pub id: String,
     pub user_id: String,
@@ -16,6 +17,21 @@ pub struct RefreshToken {
     pub is_mfa: bool,
 }
 
+impl Debug for RefreshToken {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}(...), user_id: {}, nbf: {}, exp: {}, scope: {:?}, is_mfa: {}",
+            &self.id[..5],
+            self.user_id,
+            self.nbf,
+            self.exp,
+            self.scope,
+            self.is_mfa
+        )
+    }
+}
+
 // CRUD
 impl RefreshToken {
     pub async fn create(
diff --git a/src/models/src/entity/refresh_tokens_devices.rs b/src/models/src/entity/refresh_tokens_devices.rs
index 92c948f3f..8500329fc 100644
--- a/src/models/src/entity/refresh_tokens_devices.rs
+++ b/src/models/src/entity/refresh_tokens_devices.rs
@@ -5,9 +5,10 @@ use rauthy_common::is_hiqlite;
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
+use std::fmt::{Debug, Formatter};
 use time::OffsetDateTime;
 
-#[derive(Debug, FromRow, Serialize, Deserialize)]
+#[derive(FromRow, Serialize, Deserialize)]
 pub struct RefreshTokenDevice {
     pub id: String,
     pub device_id: String,
@@ -17,6 +18,20 @@ pub struct RefreshTokenDevice {
     pub scope: Option<String>,
 }
 
+impl Debug for RefreshTokenDevice {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}(...), device_id: {}, nbf: {}, exp: {}, scope: {:?}",
+            &self.id[..5],
+            self.device_id,
+            self.nbf,
+            self.exp,
+            self.scope,
+        )
+    }
+}
+
 // CRUD
 impl RefreshTokenDevice {
     pub async fn create(
diff --git a/src/models/src/entity/sessions.rs b/src/models/src/entity/sessions.rs
index 80c452517..3f9fa9e6d 100644
--- a/src/models/src/entity/sessions.rs
+++ b/src/models/src/entity/sessions.rs
@@ -17,14 +17,14 @@ use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::{query_as, FromRow, Row};
 use std::borrow::Cow;
+use std::fmt::{Debug, Formatter};
 use std::net::IpAddr;
 use std::ops::Add;
 use std::str::FromStr;
 use time::OffsetDateTime;
 use tracing::{error, warn};
 
-// TODO impl `Debug` manually to never possibly leak the full id / csrf_token
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
+#[derive(Clone, FromRow, Serialize, Deserialize)]
 pub struct Session {
     pub id: String,
     pub csrf_token: String,
@@ -38,6 +38,26 @@ pub struct Session {
     pub remote_ip: Option<String>,
 }
 
+impl Debug for Session {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}(...), csrf_token: {}(...), user_id: {:?}, roles: {:?}, groups: {:?}, \
+        is_mfa: {}, state: {}, exp: {}, last_seen: {}, remote_ip: {:?}",
+            &self.id[..5],
+            &self.csrf_token[..5],
+            self.user_id,
+            self.roles,
+            self.groups,
+            self.is_mfa,
+            self.state,
+            self.exp,
+            self.last_seen,
+            self.remote_ip
+        )
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub enum SessionState {
     Open,
diff --git a/src/models/src/entity/users.rs b/src/models/src/entity/users.rs
index 9755934d9..ffc7adb8b 100644
--- a/src/models/src/entity/users.rs
+++ b/src/models/src/entity/users.rs
@@ -34,6 +34,7 @@ use rauthy_common::utils::{new_store_id, real_ip_from_req};
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::{query_as, FromRow};
+use std::fmt::{Debug, Formatter};
 use std::ops::Add;
 use time::OffsetDateTime;
 use tracing::{debug, error, trace, warn};
@@ -64,7 +65,7 @@ impl From<AccountType> for UserAccountTypeResponse {
     }
 }
 
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
+#[derive(Clone, FromRow, Serialize, Deserialize)]
 pub struct User {
     pub id: String,
     pub email: String,
@@ -87,6 +88,37 @@ pub struct User {
     pub federation_uid: Option<String>,
 }
 
+impl Debug for User {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "id: {}, email: {}, given_name: {}, family_name: {:?}, password: <hidden>, \
+        roles: {}, groups: {:?}, enabled: {}, email_verified: {}, password_expires: {:?}, \
+        created_at: {}, last_login: {:?}, last_failed_login: {:?}, failed_login_attempts: {:?}, \
+        language: {}, webauthn_user_id: {:?}, user_expires: {:?}, auth_provider_id: {:?}, \
+        federation_uid: {:?}",
+            self.id,
+            self.email,
+            self.given_name,
+            self.family_name,
+            self.roles,
+            self.groups,
+            self.enabled,
+            self.email_verified,
+            self.password_expires,
+            self.created_at,
+            self.last_login,
+            self.last_failed_login,
+            self.failed_login_attempts,
+            self.language,
+            self.webauthn_user_id,
+            self.user_expires,
+            self.auth_provider_id,
+            self.federation_uid
+        )
+    }
+}
+
 // CRUD
 impl User {
     pub async fn invalidate_cache(user_id: &str, email: &str) -> Result<(), ErrorResponse> {
diff --git a/src/models/src/entity/webauthn.rs b/src/models/src/entity/webauthn.rs
index 4c428781a..589eec44f 100644
--- a/src/models/src/entity/webauthn.rs
+++ b/src/models/src/entity/webauthn.rs
@@ -24,6 +24,7 @@ use rauthy_common::utils::{base64_encode, get_rand};
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
+use std::fmt::{Debug, Formatter};
 use std::ops::Add;
 use std::str::FromStr;
 use time::OffsetDateTime;
@@ -34,7 +35,7 @@ use webauthn_rs_proto::{
     AuthenticatorSelectionCriteria, ResidentKeyRequirement, UserVerificationPolicy,
 };
 
-#[derive(Debug, Clone, FromRow, Deserialize, Serialize)]
+#[derive(Clone, FromRow, Deserialize, Serialize)]
 pub struct PasskeyEntity {
     pub user_id: String,
     pub name: String,
@@ -46,6 +47,22 @@ pub struct PasskeyEntity {
     pub user_verified: Option<bool>,
 }
 
+impl Debug for PasskeyEntity {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "user_id: {}, name: {}, passkey_user_id: {}, passkey: <hidden>, \
+        credential_id: <hidden>, registered: {}, last_used: {}, user_verified: {:?}",
+            self.user_id,
+            self.name,
+            self.passkey_user_id,
+            self.registered,
+            self.last_used,
+            self.user_verified
+        )
+    }
+}
+
 // CRUD
 impl PasskeyEntity {
     /// If the `User` is `Some(_)`, a `User::save()` will be included in the `txn`


Test patch: