A library for ATProtocol identities.

feature: Adds the zeroize feature to project crates

Signed-off-by: Nick Gerakines <nick.gerakines@gmail.com>

Changed files
+171 -35
crates
atproto-identity
atproto-oauth
atproto-oauth-aip
atproto-oauth-axum
+12 -5
CLAUDE.md
··· 23 23 #### Identity Management 24 24 - **Resolve identities**: `cargo run --features clap --bin atproto-identity-resolve -- <handle_or_did>` 25 25 - **Resolve with DID document**: `cargo run --features clap --bin atproto-identity-resolve -- --did-document <handle_or_did>` 26 - - **Generate keys**: `cargo run --features clap --bin atproto-identity-key -- generate p256` 26 + - **Generate keys**: `cargo run --features clap --bin atproto-identity-key -- generate p256` (supports p256, p384, k256) 27 27 - **Sign data**: `cargo run --features clap --bin atproto-identity-sign -- <did_key> <json_file>` 28 28 - **Validate signatures**: `cargo run --features clap --bin atproto-identity-validate -- <did_key> <json_file> <signature>` 29 29 ··· 44 44 ## Architecture 45 45 46 46 A comprehensive Rust workspace with multiple crates: 47 - - **atproto-identity**: Core identity management with 8 modules (resolve, plc, web, model, validation, config, errors, key) 47 + - **atproto-identity**: Core identity management with 11 modules (resolve, plc, web, model, validation, config, errors, key, storage, storage_lru, axum) 48 48 - **atproto-record**: Record signature operations and validation 49 49 - **atproto-client**: HTTP client with OAuth and identity integration 50 50 - **atproto-jetstream**: WebSocket event streaming with compression 51 - - **atproto-oauth**: OAuth workflow implementation with storage 51 + - **atproto-oauth**: OAuth workflow implementation with DPoP, PKCE, JWT, and storage abstractions 52 + - **atproto-oauth-aip**: AT Protocol OAuth AIP (Identity Provider) implementation with PAR support 52 53 - **atproto-oauth-axum**: Axum web framework integration for OAuth 53 54 - **atproto-xrpcs**: XRPC service framework 54 55 - **atproto-xrpcs-helloworld**: Complete example XRPC service ··· 59 60 - **Comprehensive error handling** with structured error types 60 61 - **Full test coverage** with unit tests across all modules 61 62 - **Modern dependencies** for HTTP, DNS, JSON, cryptographic operations, and WebSocket streaming 63 + - **Storage abstractions** with LRU caching support (optional via `lru` feature) 64 + - **Axum integration** for web framework support (optional via `axum` feature) 65 + - **Secure memory handling** (optional via `zeroize` feature) 62 66 63 67 ## Error Handling 64 68 ··· 138 142 - **`src/validation.rs`**: Input validation for handles and DIDs 139 143 - **`src/config.rs`**: Configuration management and environment variable handling 140 144 - **`src/errors.rs`**: Structured error types following project conventions 141 - - **`src/key.rs`**: Cryptographic key operations including signature validation and key identification for P-256 and K-256 curves 145 + - **`src/key.rs`**: Cryptographic key operations including signature validation and key identification for P-256, P-384, and K-256 curves 146 + - **`src/storage.rs`**: Storage abstraction interface for DID document caching 147 + - **`src/storage_lru.rs`**: LRU-based storage implementation (requires `lru` feature) 148 + - **`src/axum.rs`**: Axum web framework integration (requires `axum` feature) 142 149 143 150 ### CLI Tools (require --features clap) 144 151 145 152 #### Identity Management (atproto-identity) 146 153 - **`src/bin/atproto-identity-resolve.rs`**: Resolve AT Protocol handles and DIDs to canonical identifiers 147 - - **`src/bin/atproto-identity-key.rs`**: Generate and manage cryptographic keys (P-256, K-256) 154 + - **`src/bin/atproto-identity-key.rs`**: Generate and manage cryptographic keys (P-256, P-384, K-256) 148 155 - **`src/bin/atproto-identity-sign.rs`**: Create cryptographic signatures of JSON data 149 156 - **`src/bin/atproto-identity-validate.rs`**: Validate cryptographic signatures 150 157
+18
Cargo.lock
··· 153 153 "thiserror 2.0.12", 154 154 "tokio", 155 155 "tracing", 156 + "zeroize", 156 157 ] 157 158 158 159 [[package]] ··· 207 208 "tokio", 208 209 "tracing", 209 210 "ulid", 211 + "zeroize", 210 212 ] 211 213 212 214 [[package]] ··· 220 222 "serde", 221 223 "serde_json", 222 224 "thiserror 2.0.12", 225 + "zeroize", 223 226 ] 224 227 225 228 [[package]] ··· 249 252 "tokio", 250 253 "tracing", 251 254 "urlencoding", 255 + "zeroize", 252 256 ] 253 257 254 258 [[package]] ··· 3397 3401 version = "1.8.1" 3398 3402 source = "registry+https://github.com/rust-lang/crates.io-index" 3399 3403 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 3404 + dependencies = [ 3405 + "zeroize_derive", 3406 + ] 3407 + 3408 + [[package]] 3409 + name = "zeroize_derive" 3410 + version = "1.4.2" 3411 + source = "registry+https://github.com/rust-lang/crates.io-index" 3412 + checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 3413 + dependencies = [ 3414 + "proc-macro2", 3415 + "quote", 3416 + "syn", 3417 + ] 3400 3418 3401 3419 [[package]] 3402 3420 name = "zerotrie"
+2
Cargo.toml
··· 65 65 urlencoding = "2.1" 66 66 zstd = "0.13" 67 67 68 + zeroize = { version = "1.8.1", features = ["zeroize_derive"] } 69 + 68 70 [workspace.lints.rust] 69 71 unsafe_code = "forbid"
+1 -1
Dockerfile
··· 24 24 # - atproto-oauth-axum: 1 binary (oauth-tool) 25 25 # - atproto-xrpcs-helloworld: 1 binary (xrpcs-helloworld) 26 26 # - atproto-jetstream: 1 binary (jetstream-consumer) 27 - RUN cargo build --release --bins -F clap 27 + RUN cargo build --release --bins -F clap,zeroize 28 28 29 29 # Runtime stage - use distroless for minimal attack surface 30 30 FROM gcr.io/distroless/cc-debian12
+3
crates/atproto-identity/Cargo.toml
··· 66 66 axum = { version = "0.8", optional = true, features = ["macros"] } 67 67 http = { version = "1.0.0", optional = true } 68 68 69 + zeroize = { workspace = true, optional = true } 70 + 69 71 [features] 70 72 default = ["lru", "axum"] 71 73 lru = ["dep:lru"] 72 74 axum = ["dep:axum", "dep:http"] 73 75 clap = ["dep:clap"] 76 + zeroize = ["dep:zeroize"] 74 77 75 78 [lints] 76 79 workspace = true
+7 -2
crates/atproto-identity/src/key.rs
··· 60 60 61 61 use crate::errors::KeyError; 62 62 63 + #[cfg(feature = "zeroize")] 64 + use zeroize::{Zeroize, ZeroizeOnDrop}; 65 + 63 66 /// Cryptographic key types supported for AT Protocol identity. 67 + #[derive(Clone, PartialEq)] 64 68 #[cfg_attr(debug_assertions, derive(Debug))] 65 - #[derive(Clone, PartialEq)] 69 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 66 70 pub enum KeyType { 67 71 /// A p256 (P-256 / secp256r1 / ES256) public key. 68 72 /// The multibase / multicodec prefix is 8024. ··· 114 118 /// * `private_dpop_key_data` 115 119 /// 116 120 #[derive(Clone)] 121 + #[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))] 117 122 pub struct KeyData(pub KeyType, pub Vec<u8>); 118 123 119 124 impl KeyData { ··· 134 139 135 140 /// Consumes self and returns the key type and bytes as a tuple. 136 141 pub fn into_parts(self) -> (KeyType, Vec<u8>) { 137 - (self.0, self.1) 142 + (self.0.clone(), self.1.clone()) 138 143 } 139 144 } 140 145
+5
crates/atproto-oauth-aip/Cargo.toml
··· 24 24 serde.workspace = true 25 25 thiserror.workspace = true 26 26 27 + zeroize = { workspace = true, optional = true } 28 + 29 + [features] 30 + zeroize = ["dep:zeroize", "atproto-oauth/zeroize"] 31 + 27 32 [lints] 28 33 workspace = true
+28 -4
crates/atproto-oauth-aip/src/workflow.rs
··· 117 117 //! for each phase of the OAuth flow including network failures, parsing errors, 118 118 //! and protocol violations. 119 119 120 - use crate::errors::OAuthWorkflowError; 121 120 use anyhow::Result; 122 121 use atproto_oauth::{ 123 122 resources::{AuthorizationServer, OAuthProtectedResource}, ··· 125 124 }; 126 125 use serde::Deserialize; 127 126 127 + use crate::errors::OAuthWorkflowError; 128 + 129 + #[cfg(feature = "zeroize")] 130 + use zeroize::{Zeroize, ZeroizeOnDrop}; 131 + 128 132 /// OAuth client configuration containing essential client credentials. 133 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 129 134 pub struct OAuthClient { 130 135 /// The redirect URI where the authorization server will send the user after authorization. 136 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 131 137 pub redirect_uri: String, 138 + 132 139 /// The unique client identifier for this OAuth client. 140 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 133 141 pub client_id: String, 134 142 135 143 /// The client secret used for authenticating with the authorization server. ··· 151 159 /// This structure contains all the information needed to make authenticated 152 160 /// requests to AT Protocol services after a successful OAuth flow. 153 161 #[derive(Clone, Deserialize)] 162 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 154 163 pub struct ATProtocolSession { 155 164 /// The Decentralized Identifier (DID) of the authenticated user. 165 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 156 166 pub did: String, 167 + 157 168 /// The handle (username) of the authenticated user. 169 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 158 170 pub handle: String, 171 + 159 172 /// The OAuth access token for making authenticated requests. 160 173 pub access_token: String, 174 + 161 175 /// The type of token (typically "Bearer"). 162 176 pub token_type: String, 177 + 163 178 /// The list of OAuth scopes granted to this session. 179 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 164 180 pub scopes: Vec<String>, 181 + 165 182 /// The Personal Data Server (PDS) endpoint URL for this user. 183 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 166 184 pub pds_endpoint: String, 185 + 167 186 /// The DPoP (Demonstration of Proof-of-Possession) key in JWK format. 168 187 pub dpop_key: String, 188 + 169 189 /// Unix timestamp indicating when this session expires. 190 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 170 191 pub expires_at: i64, 171 192 } 172 193 173 194 #[derive(Deserialize, Clone)] 195 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 174 196 #[serde(untagged)] 175 197 enum WrappedATProtocolSession { 176 198 ATProtocolSession(ATProtocolSession), 199 + 200 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 177 201 Error { 178 202 error: String, 179 203 error_description: Option<String>, ··· 411 435 .map_err(OAuthWorkflowError::SessionResponseParseFailed)?; 412 436 413 437 match response { 414 - WrappedATProtocolSession::ATProtocolSession(value) => Ok(value), 438 + WrappedATProtocolSession::ATProtocolSession(ref value) => Ok(value.clone()), 415 439 WrappedATProtocolSession::Error { 416 - error, 417 - error_description, 440 + ref error, 441 + ref error_description, 418 442 } => { 419 443 let error_message = if let Some(value) = error_description { 420 444 format!("{error}: {value}")
+3
crates/atproto-oauth-axum/Cargo.toml
··· 47 47 rpassword = { workspace = true, optional = true } 48 48 secrecy = { workspace = true, optional = true } 49 49 50 + zeroize = { workspace = true, optional = true } 51 + 50 52 [features] 51 53 clap = ["dep:clap", "dep:rpassword", "dep:secrecy"] 54 + zeroize = ["dep:zeroize", "atproto-identity/zeroize", "atproto-oauth/zeroize"] 52 55 53 56 [lints] 54 57 workspace = true
+1
crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs
··· 268 268 .iter() 269 269 .filter_map(|value| to_public(value).ok()) 270 270 .collect(), 271 + scope: None, 271 272 client_name: None, 272 273 client_uri: None, 273 274 logo_uri: None,
+3 -3
crates/atproto-oauth-axum/src/handle_complete.rs
··· 92 92 let private_dpop_key_data = identify_key(&oauth_request.dpop_private_key)?; 93 93 94 94 let oauth_client = OAuthClient { 95 - redirect_uri: oauth_client_config.redirect_uris, 96 - client_id: oauth_client_config.client_id, 95 + redirect_uri: oauth_client_config.redirect_uris.clone(), 96 + client_id: oauth_client_config.client_id.clone(), 97 97 private_signing_key_data, 98 98 }; 99 99 ··· 127 127 token_response.token_type, 128 128 token_response.expires_in, 129 129 token_response.scope, 130 - token_response.sub.unwrap_or("unknown".to_string()), 130 + token_response.sub.clone().unwrap_or("unknown".to_string()), 131 131 private_dpop_key_data 132 132 ); 133 133
-4
crates/atproto-oauth-axum/src/handle_init.rs
··· 1 - //! OAuth authorization initiation handler (placeholder). 2 - //! 3 - //! Reserved module for OAuth authorization initiation endpoints. 4 - //! Currently unused but available for future implementation.
-1
crates/atproto-oauth-axum/src/lib.rs
··· 35 35 36 36 pub mod errors; 37 37 pub mod handle_complete; 38 - pub mod handle_init; 39 38 pub mod handle_jwks; 40 39 pub mod handler_metadata; 41 40 pub mod state;
+21
crates/atproto-oauth-axum/src/state.rs
··· 8 8 use http::request::Parts; 9 9 use std::convert::Infallible; 10 10 11 + #[cfg(feature = "zeroize")] 12 + use zeroize::{Zeroize, ZeroizeOnDrop}; 13 + 11 14 /// OAuth client configuration for Axum handlers. 12 15 /// 13 16 /// Contains the essential configuration needed for OAuth client operations. 14 17 #[derive(Clone, Default)] 18 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 15 19 pub struct OAuthClientConfig { 16 20 /// OAuth client identifier 21 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 17 22 pub client_id: String, 23 + 18 24 /// Allowed OAuth redirect URIs 25 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 19 26 pub redirect_uris: String, 27 + 20 28 /// JSON Web Key Set URI for public keys 29 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 21 30 pub jwks_uri: Option<String>, 31 + 22 32 /// Signing keys for JWT operations 23 33 pub signing_keys: Vec<KeyData>, 34 + 24 35 /// OAuth scope, defaults to "atproto transition:generic" 36 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 25 37 pub scope: Option<String>, 26 38 27 39 /// Optional human-readable client name 40 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 28 41 pub client_name: Option<String>, 42 + 29 43 /// Optional client website URI 44 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 30 45 pub client_uri: Option<String>, 46 + 31 47 /// Optional client logo URI 48 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 32 49 pub logo_uri: Option<String>, 50 + 33 51 /// Optional terms of service URI 52 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 34 53 pub tos_uri: Option<String>, 54 + 35 55 /// Optional privacy policy URI 56 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 36 57 pub policy_uri: Option<String>, 37 58 } 38 59
+3
crates/atproto-oauth/Cargo.toml
··· 44 44 axum = { version = "0.8", optional = true } 45 45 http = { version = "1.0.0", optional = true } 46 46 47 + zeroize = { workspace = true, optional = true } 48 + 47 49 [features] 48 50 default = ["lru", "axum"] 49 51 lru = ["dep:lru"] 50 52 axum = ["dep:axum", "dep:http"] 53 + zeroize = ["dep:zeroize", "atproto-identity/zeroize"] 51 54 52 55 [lints] 53 56 workspace = true
+2 -2
crates/atproto-oauth/src/dpop.rs
··· 342 342 type_: Some("dpop+jwt".to_string()), 343 343 algorithm: Some("ES256".to_string()), 344 344 json_web_key: Some(dpop_jwk), 345 - ..Default::default() 345 + key_id: None, 346 346 }; 347 347 348 348 let auth = access_token.map(challenge); ··· 1252 1252 type_: Some("dpop+jwt".to_string()), 1253 1253 algorithm: Some("ES256".to_string()), 1254 1254 json_web_key: Some(dpop_jwk), 1255 - ..Default::default() 1255 + key_id: None, 1256 1256 }; 1257 1257 1258 1258 let claims = Claims::new(JoseClaims {
+8 -1
crates/atproto-oauth/src/jwk.rs
··· 15 15 16 16 use crate::errors::JWKError; 17 17 18 + #[cfg(feature = "zeroize")] 19 + use zeroize::{Zeroize, ZeroizeOnDrop}; 20 + 18 21 /// A wrapped JSON Web Key with additional metadata. 22 + #[derive(Serialize, Deserialize, Clone, PartialEq)] 19 23 #[cfg_attr(debug_assertions, derive(Debug))] 20 - #[derive(Serialize, Deserialize, Clone, PartialEq)] 24 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 21 25 pub struct WrappedJsonWebKey { 22 26 /// Key identifier (kid) for the JWK. 23 27 #[serde(skip_serializing_if = "Option::is_none", default)] 28 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 24 29 pub kid: Option<String>, 25 30 26 31 /// Algorithm (alg) used with this key. 27 32 #[serde(skip_serializing_if = "Option::is_none", default)] 33 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 28 34 pub alg: Option<String>, 29 35 30 36 /// Public key use (use) parameter, typically "sig" for signature operations. 31 37 #[serde(rename = "use", skip_serializing_if = "Option::is_none")] 38 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 32 39 pub _use: Option<String>, 33 40 34 41 /// The underlying elliptic curve JWK.
+12 -4
crates/atproto-oauth/src/jwt.rs
··· 4 4 //! custom extensions for AT Protocol OAuth flows. Supports ES256, ES384, and ES256K elliptic curve 5 5 //! signature algorithms with comprehensive timestamp validation and structured error handling. 6 6 7 - use crate::encoding::ToBase64; 8 - use crate::errors::JWTError; 9 7 use anyhow::Result; 10 8 use atproto_identity::key::{KeyData, KeyType, sign, to_public, validate}; 11 9 use base64::{Engine as _, engine::general_purpose}; ··· 14 12 use std::collections::BTreeMap; 15 13 use std::time::{SystemTime, UNIX_EPOCH}; 16 14 15 + use crate::encoding::ToBase64; 16 + use crate::errors::JWTError; 17 + 18 + #[cfg(feature = "zeroize")] 19 + use zeroize::{Zeroize, ZeroizeOnDrop}; 20 + 17 21 /// JWT header containing algorithm and key metadata. 22 + #[derive(Clone, Default, PartialEq, Serialize, Deserialize)] 18 23 #[cfg_attr(debug_assertions, derive(Debug))] 19 - #[derive(Clone, Default, PartialEq, Serialize, Deserialize)] 24 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 20 25 pub struct Header { 21 26 /// Algorithm used for signing (e.g., "ES256", "ES384", "ES256K"). 22 27 #[serde(rename = "alg", skip_serializing_if = "Option::is_none")] ··· 175 180 (Some("ES384"), KeyType::P384Private) | (Some("ES384"), KeyType::P384Public) => {} 176 181 _ => { 177 182 return Err(JWTError::UnsupportedAlgorithm { 178 - algorithm: header.algorithm.unwrap_or_else(|| "none".to_string()), 183 + algorithm: header 184 + .algorithm 185 + .clone() 186 + .unwrap_or_else(|| "none".to_string()), 179 187 key_type: format!("{}", key_data.key_type()), 180 188 } 181 189 .into());
+42 -8
crates/atproto-oauth/src/workflow.rs
··· 76 76 //! ).await?; 77 77 //! ``` 78 78 79 - use crate::{ 80 - dpop::{DpopRetry, auth_dpop}, 81 - errors::OAuthClientError, 82 - jwt::{Claims, Header, JoseClaims, mint}, 83 - resources::{AuthorizationServer, pds_resources}, 84 - }; 85 79 use atproto_identity::key::KeyData; 86 80 use chrono::{DateTime, Utc}; 87 81 use rand::distributions::{Alphanumeric, DistString}; 88 82 use reqwest_chain::ChainMiddleware; 89 83 use reqwest_middleware::ClientBuilder; 90 - 84 + use serde::Deserialize; 91 85 use std::collections::HashMap; 92 86 93 - use serde::Deserialize; 87 + #[cfg(feature = "zeroize")] 88 + use zeroize::{Zeroize, ZeroizeOnDrop}; 89 + 90 + use crate::{ 91 + dpop::{DpopRetry, auth_dpop}, 92 + errors::OAuthClientError, 93 + jwt::{Claims, Header, JoseClaims, mint}, 94 + resources::{AuthorizationServer, pds_resources}, 95 + }; 94 96 95 97 /// Response from a Pushed Authorization Request (PAR) endpoint. 96 98 /// ··· 127 129 /// 128 130 /// This struct holds the client configuration needed for OAuth authorization flows, 129 131 /// including the redirect URI, client identifier, and signing key. 132 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 130 133 pub struct OAuthClient { 131 134 /// The redirect URI where the authorization server will send the user after authorization. 135 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 132 136 pub redirect_uri: String, 137 + 133 138 /// The unique client identifier for this OAuth client. 139 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 134 140 pub client_id: String, 141 + 135 142 /// The private key data used for signing client assertions. 136 143 pub private_signing_key_data: KeyData, 137 144 } ··· 141 148 /// This struct contains all the necessary information to track and complete 142 149 /// an OAuth authorization request, including security parameters and timing. 143 150 #[derive(Clone, PartialEq)] 151 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 144 152 pub struct OAuthRequest { 145 153 /// The OAuth state parameter used to prevent CSRF attacks. 154 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 146 155 pub oauth_state: String, 156 + 147 157 /// The authorization server issuer identifier. 158 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 148 159 pub issuer: String, 160 + 149 161 /// The DID (Decentralized Identifier) of the user. 162 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 150 163 pub did: String, 164 + 151 165 /// The nonce value for additional security. 166 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 152 167 pub nonce: String, 168 + 153 169 /// The PKCE code verifier for this authorization request. 170 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 154 171 pub pkce_verifier: String, 172 + 155 173 /// The public key used for signing (serialized). 156 174 pub signing_public_key: String, 175 + 157 176 /// The DPoP private key (serialized). 177 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 158 178 pub dpop_private_key: String, 179 + 159 180 /// When this OAuth request was created. 181 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 160 182 pub created_at: DateTime<Utc>, 183 + 161 184 /// When this OAuth request expires. 185 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 162 186 pub expires_at: DateTime<Utc>, 163 187 } 164 188 ··· 183 207 /// This struct represents the successful response from an OAuth token exchange, 184 208 /// containing the access token and related metadata. 185 209 #[derive(Clone, Deserialize)] 210 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 186 211 pub struct TokenResponse { 187 212 /// The access token that can be used to access protected resources. 188 213 pub access_token: String, 214 + 189 215 /// The type of token, typically "Bearer" or "DPoP". 190 216 pub token_type: String, 217 + 191 218 /// The refresh token that can be used to obtain new access tokens. 192 219 pub refresh_token: String, 220 + 193 221 /// The scope of access granted by the access token. 222 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 194 223 pub scope: String, 224 + 195 225 /// The lifetime of the access token in seconds. 226 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 196 227 pub expires_in: u32, 228 + 197 229 /// The subject identifier (usually the user's DID). 230 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 198 231 pub sub: Option<String>, 199 232 200 233 /// Additional fields returned by the authorization server. 201 234 #[serde(flatten)] 235 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 202 236 pub extra: HashMap<String, serde_json::Value>, 203 237 } 204 238