+12
-5
CLAUDE.md
+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
+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
+2
Cargo.toml
+1
-1
Dockerfile
+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
+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
+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
+5
crates/atproto-oauth-aip/Cargo.toml
+28
-4
crates/atproto-oauth-aip/src/workflow.rs
+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
+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
+1
crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs
+3
-3
crates/atproto-oauth-axum/src/handle_complete.rs
+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
-4
crates/atproto-oauth-axum/src/handle_init.rs
-1
crates/atproto-oauth-axum/src/lib.rs
-1
crates/atproto-oauth-axum/src/lib.rs
+21
crates/atproto-oauth-axum/src/state.rs
+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
+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
+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
+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
+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
+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