1use crate::error::AuthError;
2use bech32::{self, u5, ToBase32};
3use cosmwasm_std::{Timestamp, Addr, MessageInfo, Env};
4#[cfg(target_arch = "wasm32")]
5use cosmwasm_std::{Api, ExternalApi};
6#[cfg(not(target_arch = "wasm32"))]
7use cosmwasm_crypto::{secp256k1_verify};
8use ripemd::{Digest, Ripemd160};
9use serde::de::DeserializeOwned;
10use serde::{Serialize, Deserialize};
11use sha2::Sha256;
12use std::str::from_utf8;
13use schemars::JsonSchema;
14
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
16pub struct MsgWithAuth<T> {
18 pub authorization: Authorization,
19 pub message: T,
20}
21
22#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
23pub struct Authorization {
25 pub document: String,
26 pub signature: String,
27 pub pubkey: String,
28}
29
30#[derive(Deserialize, Clone, Debug)]
31pub struct Authorized<S, T> {
33 pub auth_token: AuthToken<S>,
34 pub message: T,
35}
36
37#[derive(Deserialize, Clone, Debug)]
38pub struct AuthToken<T> {
40 pub user: Addr,
41 pub agent: Addr,
42 pub expires: u64,
43 pub meta: T,
44}
45
46#[derive(Deserialize)]
47struct SignDoc {
48 msgs: [SignMessage; 1],
49}
50
51#[derive(Deserialize)]
52struct SignMessage {
53 value: SignValue,
54}
55
56#[derive(Deserialize)]
57struct SignValue {
58 signer: Addr,
59 data: String,
60}
61
62pub fn authorize<M, A: DeserializeOwned>(
67 message: MsgWithAuth<M>,
68 info: &MessageInfo,
69 env: &Env,
70) -> Result<Authorized<A, M>, AuthError> {
71 Ok(Authorized {
72 message: message.message,
73 auth_token: validate(message.authorization, &info.sender, env.block.time)?,
74 })
75}
76
77pub fn validate<A: DeserializeOwned>(
83 authorization: Authorization,
84 provider: &Addr,
85 block_time: Timestamp,
86) -> Result<AuthToken<A>, AuthError> {
87 let pubkey = base64::decode(authorization.pubkey)?;
88 let document = base64::decode(authorization.document)?;
89 let signature = base64::decode(authorization.signature)?;
90 validate_document_signature(&document, &signature, &pubkey)?;
91
92 let document = from_utf8(&document)?;
93 let document: SignDoc = serde_json_wasm::from_str(document)?;
94 let signer = &document.msgs[0].value.signer;
95 validate_signer_pubkey(signer, pubkey)?;
96
97 let auth_token = extract_token(&document)?;
98 validate_token_expires(&auth_token, block_time)?;
99 validate_token_user(&auth_token, signer)?;
100 validate_token_agent(&auth_token, provider)?;
101
102 Ok(auth_token)
103}
104
105fn extract_token<A: DeserializeOwned>(
107 document: &SignDoc,
108) -> Result<AuthToken<A>, AuthError> {
109 let token = &document.msgs[0].value.data;
110 let token = base64::decode(token)?;
111 let token = from_utf8(&token)?.to_string();
112 Ok(serde_json_wasm::from_str(&token)?)
113}
114
115fn validate_token_expires<A>(
117 token: &AuthToken<A>,
118 block_time: Timestamp,
119) -> Result<bool, AuthError> {
120 if token.expires > block_time.seconds() {
121 Ok(true)
122 } else {
123 Err(AuthError::TokenExpired)
124 }
125}
126
127fn validate_token_user<A>(token: &AuthToken<A>, signer: &Addr) -> Result<bool, AuthError> {
129 if token.user == *signer {
130 Ok(true)
131 } else {
132 Err(AuthError::TokenAddressMismatch)
133 }
134}
135
136fn validate_token_agent<A>(token: &AuthToken<A>, agent: &Addr) -> Result<bool, AuthError> {
138 if token.agent == *agent {
139 Ok(true)
140 } else {
141 Err(AuthError::TokenAddressMismatch)
142 }
143}
144
145fn validate_document_signature(
147 document: &[u8],
148 signature: &[u8],
149 pubkey: &[u8],
150) -> Result<bool, AuthError> {
151 let mut hasher = Sha256::new();
152 hasher.update(&document);
153 let document_hash: &[u8] = &hasher.finalize();
154
155 #[cfg(target_arch = "wasm32")]
156 let verification = {
157 let api = ExternalApi {};
158 api.secp256k1_verify(document_hash, signature, pubkey)
159 };
160
161 #[cfg(not(target_arch = "wasm32"))]
162 let verification = secp256k1_verify(document_hash, signature, pubkey);
163
164 if let Ok(true) = verification {
165 Ok(true)
166 } else {
167 Err(AuthError::InvalidSignature)
168 }
169}
170
171fn validate_signer_pubkey(address: &Addr, pubkey: Vec<u8>) -> Result<bool, AuthError> {
173 let (_hrp, data, _variant) = bech32::decode(address.as_str())?;
174 let hashed_pubkey = hash_pubkey(pubkey);
175 if data == hashed_pubkey {
176 Ok(true)
177 } else {
178 Err(AuthError::SignerPubkeyMismatch)
179 }
180}
181
182fn hash_pubkey(pubkey: Vec<u8>) -> Vec<u5> {
184 let mut hasher = Sha256::new();
185 hasher.update(&pubkey);
186 let pubkey = hasher.finalize();
187 let mut hasher = Ripemd160::new();
188 hasher.update(pubkey);
189 hasher.finalize().to_vec().to_base32()
190}