1#![allow(clippy::field_reassign_with_default)]
31
32use reqwest::{header, Method, StatusCode, Url};
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::error::Error;
36use std::fmt;
37
38const ENDPOINT: &str = "https://api.clerk.com/v1/";
40
41#[derive(Debug)]
43pub enum ClerkClientError {
44 RequestError(reqwest::Error),
46 ApiError { status: StatusCode, message: String },
48 HeaderError(reqwest::header::InvalidHeaderValue),
50 InvalidUrl(String),
52}
53
54impl fmt::Display for ClerkClientError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::RequestError(e) => write!(f, "Request error: {}", e),
58 Self::ApiError { status, message } => write!(f, "API error ({}): {}", status, message),
59 Self::HeaderError(e) => write!(f, "Header error: {}", e),
60 Self::InvalidUrl(msg) => write!(f, "Invalid URL: {}", msg),
61 }
62 }
63}
64
65impl Error for ClerkClientError {}
66
67impl From<reqwest::Error> for ClerkClientError {
68 fn from(err: reqwest::Error) -> Self {
69 Self::RequestError(err)
70 }
71}
72
73impl From<reqwest::header::InvalidHeaderValue> for ClerkClientError {
74 fn from(err: reqwest::header::InvalidHeaderValue) -> Self {
75 Self::HeaderError(err)
76 }
77}
78
79pub type Result<T> = std::result::Result<T, ClerkClientError>;
80
81pub struct ClerkClient {
83 secret_key: String,
84 client: reqwest::Client,
85}
86
87pub fn secret_key_from_env() -> String {
89 std::env::var("CLERK_SECRET_KEY").unwrap_or_default()
90}
91
92impl ClerkClient {
93 pub fn new<K>(secret_key: K) -> Result<Self>
95 where
96 K: ToString,
97 {
98 let client = reqwest::Client::builder()
99 .timeout(std::time::Duration::from_secs(30))
100 .build()?;
101
102 Ok(Self {
103 secret_key: secret_key.to_string(),
104 client,
105 })
106 }
107
108 pub fn new_from_env() -> Result<Self> {
110 ClerkClient::new(secret_key_from_env())
111 }
112
113 pub fn get_key(&self) -> &str {
115 &self.secret_key
116 }
117
118 async fn request<B>(
119 &self,
120 method: Method,
121 path: String,
122 body: B,
123 query: Option<Vec<(&str, String)>>,
124 ) -> Result<reqwest::Response>
125 where
126 B: Serialize,
127 {
128 let base = Url::parse(ENDPOINT).map_err(|e| ClerkClientError::InvalidUrl(e.to_string()))?;
129 let url = base
130 .join(&path)
131 .map_err(|e| ClerkClientError::InvalidUrl(e.to_string()))?;
132
133 let bt = format!("Bearer {}", self.secret_key);
134 let bearer = header::HeaderValue::from_str(&bt)?;
135
136 let mut headers = header::HeaderMap::new();
138 headers.append(header::AUTHORIZATION, bearer);
139 headers.append(
140 header::CONTENT_TYPE,
141 header::HeaderValue::from_static("application/json"),
142 );
143 headers.append(
144 header::USER_AGENT,
145 header::HeaderValue::from_static("libauth-rs/0.1.0"),
146 );
147
148 let mut rb = self.client.request(method.clone(), url).headers(headers);
149
150 if let Some(val) = query {
151 rb = rb.query(&val);
152 }
153
154 if method != Method::GET && method != Method::DELETE {
156 rb = rb.json(&body);
157 }
158
159 let resp = rb.send().await?;
160 Ok(resp)
161 }
162
163 pub async fn list_users(&self, params: ListUsersParams) -> Result<Vec<User>> {
166 let mut query_params = vec![];
167
168 if let Some(limit) = params.limit {
169 query_params.push(("limit", limit.to_string()));
170 }
171 if let Some(offset) = params.offset {
172 query_params.push(("offset", offset.to_string()));
173 }
174 if let Some(email_address) = params.email_address {
175 query_params.push(("email_address", email_address));
176 }
177 if let Some(phone_number) = params.phone_number {
178 query_params.push(("phone_number", phone_number));
179 }
180 if let Some(username) = params.username {
181 query_params.push(("username", username));
182 }
183 if let Some(user_id) = params.user_id {
184 query_params.push(("user_id", user_id));
185 }
186 if let Some(order_by) = params.order_by {
187 query_params.push(("order_by", order_by));
188 }
189
190 let resp = self
191 .request(Method::GET, "users".to_string(), (), Some(query_params))
192 .await?;
193
194 if !resp.status().is_success() {
195 let status = resp.status();
196 let body = resp.text().await?;
197 return Err(ClerkClientError::ApiError {
198 status,
199 message: body,
200 });
201 }
202
203 let users: Vec<User> = resp.json().await?;
204 Ok(users)
205 }
206
207 pub async fn get_user(&self, user_id: &str) -> Result<User> {
210 let resp = self
211 .request(Method::GET, format!("users/{}", user_id), (), None)
212 .await?;
213
214 if !resp.status().is_success() {
215 let status = resp.status();
216 let body = resp.text().await?;
217 return Err(ClerkClientError::ApiError {
218 status,
219 message: body,
220 });
221 }
222
223 let user: User = resp.json().await?;
224 Ok(user)
225 }
226
227 pub async fn create_user(&self, req: CreateUserRequest) -> Result<User> {
230 let resp = self
231 .request(Method::POST, "users".to_string(), req, None)
232 .await?;
233
234 if !resp.status().is_success() {
235 let status = resp.status();
236 let body = resp.text().await?;
237 return Err(ClerkClientError::ApiError {
238 status,
239 message: body,
240 });
241 }
242
243 let user: User = resp.json().await?;
244 Ok(user)
245 }
246
247 pub async fn update_user(&self, user_id: &str, req: UpdateUserRequest) -> Result<User> {
250 let resp = self
251 .request(Method::PATCH, format!("users/{}", user_id), req, None)
252 .await?;
253
254 if !resp.status().is_success() {
255 let status = resp.status();
256 let body = resp.text().await?;
257 return Err(ClerkClientError::ApiError {
258 status,
259 message: body,
260 });
261 }
262
263 let user: User = resp.json().await?;
264 Ok(user)
265 }
266
267 pub async fn delete_user(&self, user_id: &str) -> Result<DeletedObject> {
270 let resp = self
271 .request(Method::DELETE, format!("users/{}", user_id), (), None)
272 .await?;
273
274 if !resp.status().is_success() {
275 let status = resp.status();
276 let body = resp.text().await?;
277 return Err(ClerkClientError::ApiError {
278 status,
279 message: body,
280 });
281 }
282
283 let result: DeletedObject = resp.json().await?;
284 Ok(result)
285 }
286
287 pub async fn list_sessions(&self, params: ListSessionsParams) -> Result<Vec<Session>> {
290 let mut query_params = vec![];
291
292 if let Some(limit) = params.limit {
293 query_params.push(("limit", limit.to_string()));
294 }
295 if let Some(offset) = params.offset {
296 query_params.push(("offset", offset.to_string()));
297 }
298 if let Some(client_id) = params.client_id {
299 query_params.push(("client_id", client_id));
300 }
301 if let Some(user_id) = params.user_id {
302 query_params.push(("user_id", user_id));
303 }
304 if let Some(status) = params.status {
305 query_params.push(("status", status));
306 }
307
308 let resp = self
309 .request(Method::GET, "sessions".to_string(), (), Some(query_params))
310 .await?;
311
312 if !resp.status().is_success() {
313 let status = resp.status();
314 let body = resp.text().await?;
315 return Err(ClerkClientError::ApiError {
316 status,
317 message: body,
318 });
319 }
320
321 let sessions: Vec<Session> = resp.json().await?;
322 Ok(sessions)
323 }
324
325 pub async fn get_session(&self, session_id: &str) -> Result<Session> {
328 let resp = self
329 .request(Method::GET, format!("sessions/{}", session_id), (), None)
330 .await?;
331
332 if !resp.status().is_success() {
333 let status = resp.status();
334 let body = resp.text().await?;
335 return Err(ClerkClientError::ApiError {
336 status,
337 message: body,
338 });
339 }
340
341 let session: Session = resp.json().await?;
342 Ok(session)
343 }
344
345 pub async fn verify_session(&self, session_id: &str) -> Result<Session> {
348 let resp = self
349 .request(
350 Method::POST,
351 format!("sessions/{}/verify", session_id),
352 (),
353 None,
354 )
355 .await?;
356
357 if !resp.status().is_success() {
358 let status = resp.status();
359 let body = resp.text().await?;
360 return Err(ClerkClientError::ApiError {
361 status,
362 message: body,
363 });
364 }
365
366 let session: Session = resp.json().await?;
367 Ok(session)
368 }
369
370 pub async fn revoke_session(&self, session_id: &str) -> Result<Session> {
373 let resp = self
374 .request(
375 Method::POST,
376 format!("sessions/{}/revoke", session_id),
377 (),
378 None,
379 )
380 .await?;
381
382 if !resp.status().is_success() {
383 let status = resp.status();
384 let body = resp.text().await?;
385 return Err(ClerkClientError::ApiError {
386 status,
387 message: body,
388 });
389 }
390
391 let session: Session = resp.json().await?;
392 Ok(session)
393 }
394
395 pub async fn list_organizations(
398 &self,
399 params: ListOrganizationsParams,
400 ) -> Result<Vec<Organization>> {
401 let mut query_params = vec![];
402
403 if let Some(limit) = params.limit {
404 query_params.push(("limit", limit.to_string()));
405 }
406 if let Some(offset) = params.offset {
407 query_params.push(("offset", offset.to_string()));
408 }
409 if let Some(query) = params.query {
410 query_params.push(("query", query));
411 }
412
413 let resp = self
414 .request(
415 Method::GET,
416 "organizations".to_string(),
417 (),
418 Some(query_params),
419 )
420 .await?;
421
422 if !resp.status().is_success() {
423 let status = resp.status();
424 let body = resp.text().await?;
425 return Err(ClerkClientError::ApiError {
426 status,
427 message: body,
428 });
429 }
430
431 let orgs: Vec<Organization> = resp.json().await?;
432 Ok(orgs)
433 }
434
435 pub async fn get_organization(&self, organization_id: &str) -> Result<Organization> {
438 let resp = self
439 .request(
440 Method::GET,
441 format!("organizations/{}", organization_id),
442 (),
443 None,
444 )
445 .await?;
446
447 if !resp.status().is_success() {
448 let status = resp.status();
449 let body = resp.text().await?;
450 return Err(ClerkClientError::ApiError {
451 status,
452 message: body,
453 });
454 }
455
456 let org: Organization = resp.json().await?;
457 Ok(org)
458 }
459
460 pub async fn create_organization(
463 &self,
464 req: CreateOrganizationRequest,
465 ) -> Result<Organization> {
466 let resp = self
467 .request(Method::POST, "organizations".to_string(), req, None)
468 .await?;
469
470 if !resp.status().is_success() {
471 let status = resp.status();
472 let body = resp.text().await?;
473 return Err(ClerkClientError::ApiError {
474 status,
475 message: body,
476 });
477 }
478
479 let org: Organization = resp.json().await?;
480 Ok(org)
481 }
482
483 pub async fn update_organization(
486 &self,
487 organization_id: &str,
488 req: UpdateOrganizationRequest,
489 ) -> Result<Organization> {
490 let resp = self
491 .request(
492 Method::PATCH,
493 format!("organizations/{}", organization_id),
494 req,
495 None,
496 )
497 .await?;
498
499 if !resp.status().is_success() {
500 let status = resp.status();
501 let body = resp.text().await?;
502 return Err(ClerkClientError::ApiError {
503 status,
504 message: body,
505 });
506 }
507
508 let org: Organization = resp.json().await?;
509 Ok(org)
510 }
511
512 pub async fn delete_organization(&self, organization_id: &str) -> Result<DeletedObject> {
515 let resp = self
516 .request(
517 Method::DELETE,
518 format!("organizations/{}", organization_id),
519 (),
520 None,
521 )
522 .await?;
523
524 if !resp.status().is_success() {
525 let status = resp.status();
526 let body = resp.text().await?;
527 return Err(ClerkClientError::ApiError {
528 status,
529 message: body,
530 });
531 }
532
533 let result: DeletedObject = resp.json().await?;
534 Ok(result)
535 }
536}
537
538#[derive(Debug, Default, Clone)]
544pub struct ListUsersParams {
545 pub limit: Option<u32>,
546 pub offset: Option<u32>,
547 pub email_address: Option<String>,
548 pub phone_number: Option<String>,
549 pub username: Option<String>,
550 pub user_id: Option<String>,
551 pub order_by: Option<String>,
552}
553
554#[derive(Debug, Default, Clone, Serialize, Deserialize)]
556pub struct CreateUserRequest {
557 #[serde(skip_serializing_if = "Option::is_none")]
558 pub email_address: Option<Vec<String>>,
559 #[serde(skip_serializing_if = "Option::is_none")]
560 pub phone_number: Option<Vec<String>>,
561 #[serde(skip_serializing_if = "Option::is_none")]
562 pub username: Option<String>,
563 #[serde(skip_serializing_if = "Option::is_none")]
564 pub password: Option<String>,
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub first_name: Option<String>,
567 #[serde(skip_serializing_if = "Option::is_none")]
568 pub last_name: Option<String>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub external_id: Option<String>,
571 #[serde(skip_serializing_if = "Option::is_none")]
572 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
573 #[serde(skip_serializing_if = "Option::is_none")]
574 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
575 #[serde(skip_serializing_if = "Option::is_none")]
576 pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
577}
578
579#[derive(Debug, Default, Clone, Serialize, Deserialize)]
581pub struct UpdateUserRequest {
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub first_name: Option<String>,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 pub last_name: Option<String>,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub username: Option<String>,
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub primary_email_address_id: Option<String>,
590 #[serde(skip_serializing_if = "Option::is_none")]
591 pub primary_phone_number_id: Option<String>,
592 #[serde(skip_serializing_if = "Option::is_none")]
593 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
594 #[serde(skip_serializing_if = "Option::is_none")]
595 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct User {
603 pub id: String,
604 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub object: Option<String>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub username: Option<String>,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub first_name: Option<String>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub last_name: Option<String>,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub image_url: Option<String>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub has_image: Option<bool>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub primary_email_address_id: Option<String>,
618 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub primary_phone_number_id: Option<String>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
621 pub primary_web3_wallet_id: Option<String>,
622 #[serde(default)]
623 pub email_addresses: Vec<EmailAddress>,
624 #[serde(default)]
625 pub phone_numbers: Vec<PhoneNumber>,
626 #[serde(default)]
627 pub web3_wallets: Vec<Web3Wallet>,
628 #[serde(default, skip_serializing_if = "Option::is_none")]
629 pub external_id: Option<String>,
630 #[serde(default)]
631 pub public_metadata: HashMap<String, serde_json::Value>,
632 #[serde(default)]
633 pub private_metadata: HashMap<String, serde_json::Value>,
634 #[serde(default)]
635 pub unsafe_metadata: HashMap<String, serde_json::Value>,
636 pub created_at: i64,
637 pub updated_at: i64,
638 #[serde(default, skip_serializing_if = "Option::is_none")]
639 pub last_sign_in_at: Option<i64>,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub banned: Option<bool>,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub locked: Option<bool>,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct EmailAddress {
649 pub id: String,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub object: Option<String>,
652 pub email_address: String,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub verification: Option<Verification>,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub linked_to: Option<Vec<LinkedIdentity>>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct PhoneNumber {
662 pub id: String,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
664 pub object: Option<String>,
665 pub phone_number: String,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
667 pub verification: Option<Verification>,
668 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub linked_to: Option<Vec<LinkedIdentity>>,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct Web3Wallet {
675 pub id: String,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
677 pub object: Option<String>,
678 pub web3_wallet: String,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
680 pub verification: Option<Verification>,
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize)]
685pub struct Verification {
686 pub status: String,
687 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub strategy: Option<String>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub attempts: Option<i32>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub expire_at: Option<i64>,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct LinkedIdentity {
698 pub id: String,
699 #[serde(rename = "type")]
700 pub identity_type: String,
701}
702
703#[derive(Debug, Default, Clone)]
709pub struct ListSessionsParams {
710 pub limit: Option<u32>,
711 pub offset: Option<u32>,
712 pub client_id: Option<String>,
713 pub user_id: Option<String>,
714 pub status: Option<String>,
715}
716
717#[derive(Debug, Clone, Serialize, Deserialize)]
719pub struct Session {
720 pub id: String,
721 #[serde(default, skip_serializing_if = "Option::is_none")]
722 pub object: Option<String>,
723 pub client_id: String,
724 pub user_id: String,
725 pub status: String,
726 #[serde(default, skip_serializing_if = "Option::is_none")]
727 pub last_active_at: Option<i64>,
728 #[serde(default, skip_serializing_if = "Option::is_none")]
729 pub expire_at: Option<i64>,
730 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub abandon_at: Option<i64>,
732 pub created_at: i64,
733 pub updated_at: i64,
734}
735
736#[derive(Debug, Default, Clone)]
742pub struct ListOrganizationsParams {
743 pub limit: Option<u32>,
744 pub offset: Option<u32>,
745 pub query: Option<String>,
746}
747
748#[derive(Debug, Default, Clone, Serialize, Deserialize)]
750pub struct CreateOrganizationRequest {
751 pub name: String,
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub slug: Option<String>,
754 #[serde(skip_serializing_if = "Option::is_none")]
755 pub created_by: Option<String>,
756 #[serde(skip_serializing_if = "Option::is_none")]
757 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
758 #[serde(skip_serializing_if = "Option::is_none")]
759 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 pub max_allowed_memberships: Option<i32>,
762}
763
764#[derive(Debug, Default, Clone, Serialize, Deserialize)]
766pub struct UpdateOrganizationRequest {
767 #[serde(skip_serializing_if = "Option::is_none")]
768 pub name: Option<String>,
769 #[serde(skip_serializing_if = "Option::is_none")]
770 pub slug: Option<String>,
771 #[serde(skip_serializing_if = "Option::is_none")]
772 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
773 #[serde(skip_serializing_if = "Option::is_none")]
774 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
775 #[serde(skip_serializing_if = "Option::is_none")]
776 pub max_allowed_memberships: Option<i32>,
777}
778
779#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct Organization {
782 pub id: String,
783 #[serde(default, skip_serializing_if = "Option::is_none")]
784 pub object: Option<String>,
785 pub name: String,
786 pub slug: String,
787 #[serde(default, skip_serializing_if = "Option::is_none")]
788 pub image_url: Option<String>,
789 #[serde(default, skip_serializing_if = "Option::is_none")]
790 pub has_image: Option<bool>,
791 #[serde(default, skip_serializing_if = "Option::is_none")]
792 pub members_count: Option<i32>,
793 #[serde(default, skip_serializing_if = "Option::is_none")]
794 pub pending_invitations_count: Option<i32>,
795 #[serde(default, skip_serializing_if = "Option::is_none")]
796 pub max_allowed_memberships: Option<i32>,
797 #[serde(default)]
798 pub public_metadata: HashMap<String, serde_json::Value>,
799 #[serde(default)]
800 pub private_metadata: HashMap<String, serde_json::Value>,
801 pub created_at: i64,
802 pub updated_at: i64,
803}
804
805#[derive(Debug, Clone, Serialize, Deserialize)]
811pub struct DeletedObject {
812 #[serde(default, skip_serializing_if = "Option::is_none")]
813 pub object: Option<String>,
814 pub id: String,
815 #[serde(default, skip_serializing_if = "Option::is_none")]
816 pub deleted: Option<bool>,
817}
818
819#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct ClerkError {
822 #[serde(default)]
823 pub errors: Vec<ClerkErrorDetail>,
824 #[serde(default, skip_serializing_if = "Option::is_none")]
825 pub clerk_trace_id: Option<String>,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct ClerkErrorDetail {
831 pub message: String,
832 #[serde(default, skip_serializing_if = "Option::is_none")]
833 pub long_message: Option<String>,
834 pub code: String,
835 #[serde(default, skip_serializing_if = "Option::is_none")]
836 pub meta: Option<HashMap<String, serde_json::Value>>,
837}