Skip to main content

circle_user_controlled_wallets/models/
auth.rs

1//! Authentication models for the Circle User-Controlled Wallets API.
2//!
3//! Contains request and response types for device-token, email OTP, and
4//! user-token refresh flows.
5
6use serde::{Deserialize, Serialize};
7
8// ── Social device token ───────────────────────────────────────────────────────
9
10/// `data` payload for a social sign-in device-token response.
11#[derive(Debug, Clone, Deserialize, Serialize)]
12#[serde(rename_all = "camelCase")]
13pub struct DeviceTokenSocialData {
14    /// Short-lived device token for the Circle mobile SDK.
15    pub device_token: String,
16    /// Encryption key paired with the device token.
17    pub device_encryption_key: Option<String>,
18}
19
20/// Response envelope for `getDeviceTokenSocial`.
21#[derive(Debug, Clone, Deserialize, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct DeviceTokenSocialResponse {
24    /// Device token data.
25    pub data: DeviceTokenSocialData,
26}
27
28// ── Email device token ────────────────────────────────────────────────────────
29
30/// `data` payload for an email sign-in device-token response.
31#[derive(Debug, Clone, Deserialize, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct DeviceTokenEmailData {
34    /// Short-lived device token for the Circle mobile SDK.
35    pub device_token: String,
36    /// Encryption key paired with the device token.
37    pub device_encryption_key: Option<String>,
38    /// OTP token to be verified by the user.
39    pub otp_token: Option<String>,
40}
41
42/// Response envelope for `getDeviceTokenEmail`.
43#[derive(Debug, Clone, Deserialize, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub struct DeviceTokenEmailResponse {
46    /// Email device token data.
47    pub data: DeviceTokenEmailData,
48}
49
50// ── Refresh user token ────────────────────────────────────────────────────────
51
52/// `data` payload for a refreshed user token.
53#[derive(Debug, Clone, Deserialize, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub struct RefreshUserTokenData {
56    /// New short-lived user JWT.
57    pub user_token: String,
58    /// New encryption key.
59    pub encryption_key: Option<String>,
60    /// ID of the authenticated user.
61    pub user_id: Option<String>,
62    /// Opaque refresh token for the next refresh cycle.
63    pub refresh_token: Option<String>,
64}
65
66/// Response envelope for `refreshUserToken`.
67#[derive(Debug, Clone, Deserialize, Serialize)]
68#[serde(rename_all = "camelCase")]
69pub struct RefreshUserTokenResponse {
70    /// Refreshed token data.
71    pub data: RefreshUserTokenData,
72}
73
74// ── Resend OTP ────────────────────────────────────────────────────────────────
75
76/// `data` payload for a resend-OTP response.
77#[derive(Debug, Clone, Deserialize, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct ResendOtpData {
80    /// New OTP token issued to the user.
81    pub otp_token: String,
82}
83
84/// Response envelope for `resendOtp`.
85#[derive(Debug, Clone, Deserialize, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct ResendOtpResponse {
88    /// OTP data.
89    pub data: ResendOtpData,
90}
91
92// ── Request bodies ────────────────────────────────────────────────────────────
93
94/// Request body for `getDeviceTokenSocial`.
95#[derive(Debug, Clone, Deserialize, Serialize)]
96#[serde(rename_all = "camelCase")]
97pub struct DeviceTokenSocialRequest {
98    /// Client-generated idempotency key (UUID).
99    pub idempotency_key: String,
100    /// Unique identifier for the user's device.
101    pub device_id: String,
102}
103
104/// Request body for `getDeviceTokenEmail`.
105#[derive(Debug, Clone, Deserialize, Serialize)]
106#[serde(rename_all = "camelCase")]
107pub struct DeviceTokenEmailRequest {
108    /// Client-generated idempotency key (UUID).
109    pub idempotency_key: String,
110    /// Unique identifier for the user's device.
111    pub device_id: String,
112    /// Email address for OTP delivery.
113    pub email: String,
114}
115
116/// Request body for `refreshUserToken`.
117#[derive(Debug, Clone, Deserialize, Serialize)]
118#[serde(rename_all = "camelCase")]
119pub struct RefreshUserTokenRequest {
120    /// Client-generated idempotency key (UUID).
121    pub idempotency_key: String,
122    /// Refresh token from a previous authentication.
123    pub refresh_token: String,
124    /// Unique identifier for the user's device.
125    pub device_id: String,
126}
127
128/// Request body for `resendOtp`.
129#[derive(Debug, Clone, Deserialize, Serialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ResendOtpRequest {
132    /// Client-generated idempotency key (UUID).
133    pub idempotency_key: String,
134    /// Active OTP token to be superseded.
135    pub otp_token: String,
136    /// Email address for OTP delivery.
137    pub email: String,
138    /// Unique identifier for the user's device.
139    pub device_id: String,
140}
141
142// ── Tests ─────────────────────────────────────────────────────────────────────
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn device_token_social_request_camel_case() -> Result<(), Box<dyn std::error::Error>> {
150        let req = DeviceTokenSocialRequest {
151            idempotency_key: "key1".to_string(),
152            device_id: "dev1".to_string(),
153        };
154        let s = serde_json::to_string(&req)?;
155        assert!(s.contains("idempotencyKey"), "{s}");
156        assert!(s.contains("deviceId"), "{s}");
157        Ok(())
158    }
159
160    #[test]
161    fn refresh_user_token_response_round_trip() -> Result<(), Box<dyn std::error::Error>> {
162        let json = r#"{"data":{"userToken":"tok","encryptionKey":"key","userId":"u1","refreshToken":"rt"}}"#;
163        let resp: RefreshUserTokenResponse = serde_json::from_str(json)?;
164        assert_eq!(resp.data.user_token, "tok");
165        assert_eq!(resp.data.user_id.as_deref(), Some("u1"));
166        Ok(())
167    }
168}