auths_sdk/workflows/
auth.rs1use auths_core::error::AuthsErrorInfo;
2use thiserror::Error;
3
4#[derive(Debug, Clone)]
22pub struct SignedAuthChallenge {
23 pub signature_hex: String,
25 pub public_key_hex: String,
27 pub did: String,
29}
30
31#[derive(Debug, Error)]
33#[non_exhaustive]
34pub enum AuthChallengeError {
35 #[error("nonce must not be empty")]
37 EmptyNonce,
38
39 #[error("domain must not be empty")]
41 EmptyDomain,
42
43 #[error("canonical JSON serialization failed: {0}")]
45 Canonicalization(String),
46}
47
48impl AuthsErrorInfo for AuthChallengeError {
49 fn error_code(&self) -> &'static str {
50 match self {
51 Self::EmptyNonce => "AUTHS-E6001",
52 Self::EmptyDomain => "AUTHS-E6002",
53 Self::Canonicalization(_) => "AUTHS-E6003",
54 }
55 }
56
57 fn suggestion(&self) -> Option<&'static str> {
58 match self {
59 Self::EmptyNonce => Some("Provide the nonce from the authentication challenge"),
60 Self::EmptyDomain => Some("Provide the domain (e.g. auths.dev)"),
61 Self::Canonicalization(_) => {
62 Some("This is an internal error; please report it as a bug")
63 }
64 }
65 }
66}
67
68pub fn build_auth_challenge_message(
84 nonce: &str,
85 domain: &str,
86) -> Result<String, AuthChallengeError> {
87 if nonce.is_empty() {
88 return Err(AuthChallengeError::EmptyNonce);
89 }
90 if domain.is_empty() {
91 return Err(AuthChallengeError::EmptyDomain);
92 }
93 let payload = serde_json::json!({
94 "domain": domain,
95 "nonce": nonce,
96 });
97 json_canon::to_string(&payload).map_err(|e| AuthChallengeError::Canonicalization(e.to_string()))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn canonical_json_sorts_keys_alphabetically() {
106 let payload = serde_json::json!({
107 "nonce": "abc",
108 "domain": "xyz",
109 });
110 let canonical = json_canon::to_string(&payload).expect("canonical");
111 assert_eq!(canonical, r#"{"domain":"xyz","nonce":"abc"}"#);
112 }
113
114 #[test]
115 fn build_auth_challenge_message_produces_canonical_json() {
116 let msg = build_auth_challenge_message("abc123", "auths.dev").unwrap();
117 assert_eq!(msg, r#"{"domain":"auths.dev","nonce":"abc123"}"#);
118 }
119
120 #[test]
121 fn build_auth_challenge_message_rejects_empty_nonce() {
122 let err = build_auth_challenge_message("", "auths.dev").unwrap_err();
123 assert!(matches!(err, AuthChallengeError::EmptyNonce));
124 }
125
126 #[test]
127 fn build_auth_challenge_message_rejects_empty_domain() {
128 let err = build_auth_challenge_message("abc", "").unwrap_err();
129 assert!(matches!(err, AuthChallengeError::EmptyDomain));
130 }
131}