polimec_common/credentials/
mod.rs1use frame_support::{pallet_prelude::*, parameter_types, traits::OriginTrait, Deserialize, Serialize};
18use pallet_timestamp::Now;
19use parity_scale_codec::{Decode, Encode};
20use scale_info::{prelude::string::String, TypeInfo};
21use serde::{de::Error, ser::SerializeStruct, Serializer};
22use sp_runtime::{traits::BadOrigin, DeserializeOwned, RuntimeDebug};
23
24pub use jwt_compact_frame::{
25 alg::{Ed25519, VerifyingKey},
26 Claims as StandardClaims, *,
27};
28use serde::Deserializer;
29
30#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize, Serialize)]
31#[serde(rename_all = "lowercase")]
32pub enum InvestorType {
33 Retail,
34 Professional,
35 Institutional,
36}
37
38impl InvestorType {
39 #[must_use]
40 pub fn as_str(&self) -> &'static str {
41 match self {
42 InvestorType::Retail => "retail",
43 InvestorType::Professional => "professional",
44 InvestorType::Institutional => "institutional",
45 }
46 }
47}
48
49parameter_types! {
50 pub const Retail: InvestorType = InvestorType::Retail;
51 pub const Professional: InvestorType = InvestorType::Professional;
52 pub const Institutional: InvestorType = InvestorType::Institutional;
53}
54
55#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize)]
56pub struct SampleClaims<AccountId> {
57 #[serde(rename = "sub")]
58 pub subject: AccountId,
59 #[serde(rename = "iss")]
60 pub issuer: String,
61 #[serde(rename = "aud", deserialize_with = "from_bounded_cid")]
62 pub ipfs_cid: Cid,
63 pub investor_type: InvestorType,
64 #[serde(deserialize_with = "from_bounded_did")]
65 pub did: Did,
66}
67
68pub type Did = BoundedVec<u8, ConstU32<57>>;
69pub type Cid = BoundedVec<u8, ConstU32<96>>;
70
71pub struct EnsureInvestor<T>(sp_std::marker::PhantomData<T>);
72impl<T> EnsureOriginWithCredentials<T::RuntimeOrigin> for EnsureInvestor<T>
73where
74 T: frame_system::Config + pallet_timestamp::Config,
75{
76 type Claims = SampleClaims<T::AccountId>;
77 type Success = (T::AccountId, Did, InvestorType, Cid);
78
79 fn try_origin(
80 origin: T::RuntimeOrigin,
81 token: &jwt_compact_frame::UntrustedToken,
82 verifying_key: [u8; 32],
83 ) -> Result<Self::Success, T::RuntimeOrigin> {
84 let Some(who) = origin.clone().into_signer() else { return Err(origin) };
85 let Ok(token) = Self::verify_token(token, verifying_key) else { return Err(origin) };
86 let claims = token.claims();
87 let Ok(now) = Now::<T>::get().try_into() else { return Err(origin) };
89 let Some(date_time) = claims.expiration else { return Err(origin) };
90
91 let timestamp: u64 = date_time.timestamp_millis().try_into().map_err(|_| origin.clone())?;
92
93 if claims.custom.subject == who && timestamp >= now {
94 return Ok((
95 who,
96 claims.custom.did.clone(),
97 claims.custom.investor_type.clone(),
98 claims.custom.ipfs_cid.clone(),
99 ));
100 }
101
102 Err(origin)
103 }
104}
105
106#[allow(clippy::module_name_repetitions)]
107pub trait EnsureOriginWithCredentials<OuterOrigin>
108where
109 OuterOrigin: OriginTrait,
110{
111 type Success;
112 type Claims: Clone + Encode + Decode + Eq + PartialEq + Ord + PartialOrd + TypeInfo + DeserializeOwned;
113
114 fn try_origin(
115 origin: OuterOrigin,
116 token: &jwt_compact_frame::UntrustedToken,
117 verifying_key: [u8; 32],
118 ) -> Result<Self::Success, OuterOrigin>;
119
120 fn ensure_origin(
121 origin: OuterOrigin,
122 token: &jwt_compact_frame::UntrustedToken,
123 verifying_key: [u8; 32],
124 ) -> Result<Self::Success, BadOrigin> {
125 Self::try_origin(origin, token, verifying_key).map_err(|_| BadOrigin)
126 }
127
128 fn verify_token(
129 token: &jwt_compact_frame::UntrustedToken,
130 verifying_key: [u8; 32],
131 ) -> Result<jwt_compact_frame::Token<Self::Claims>, ValidationError> {
132 let signing_key =
133 <<Ed25519 as Algorithm>::VerifyingKey>::from_slice(&verifying_key).expect("The Key is always valid");
134 Ed25519.validator::<Self::Claims>(&signing_key).validate(token)
135 }
136}
137
138pub fn from_bounded_did<'de, D>(deserializer: D) -> Result<Did, D::Error>
139where
140 D: Deserializer<'de>,
141{
142 String::deserialize(deserializer)
143 .map(|string| string.as_bytes().to_vec())
144 .and_then(|vec| vec.try_into().map_err(|_| Error::custom("failed to deserialize")))
145}
146
147pub fn from_bounded_cid<'de, D>(deserializer: D) -> Result<Cid, D::Error>
148where
149 D: Deserializer<'de>,
150{
151 String::deserialize(deserializer)
152 .map(|string| string.as_bytes().to_vec())
153 .and_then(|vec| vec.try_into().map_err(|_| Error::custom("failed to deserialize")))
154}
155
156impl<AccountId> Serialize for SampleClaims<AccountId>
157where
158 AccountId: Serialize, {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: Serializer,
163 {
164 let mut state = serializer.serialize_struct("SampleClaims", 5)?;
166
167 state.serialize_field("sub", &self.subject)?;
170 state.serialize_field("iss", &self.issuer)?;
171 let ipfs_cid_bytes: scale_info::prelude::vec::Vec<u8> = self.ipfs_cid.clone().into(); let ipfs_cid_string = String::from_utf8_lossy(&ipfs_cid_bytes); state.serialize_field("aud", &ipfs_cid_string)?;
176
177 state.serialize_field("investor_type", &self.investor_type)?;
178
179 let did_bytes: scale_info::prelude::vec::Vec<u8> = self.did.clone().into(); let did_string = String::from_utf8_lossy(&did_bytes); state.serialize_field("did", &did_string)?;
184
185 state.end()
187 }
188}