use crate::{
delegation::{
create_delegation, create_delegation_hash, create_user_canister_pubkey, generate_seed,
DelegationError,
},
hash,
rand::generate_nonce,
settings::Settings,
signature_map::SignatureMap,
siws::{SiwsMessage, SiwsMessageError},
solana::{verify_sol_signature, SolError, SolPubkey, SolSignature},
time::get_current_time,
with_settings, SIWS_MESSAGES,
};
use candid::{CandidType, Principal};
use serde::Deserialize;
use serde_bytes::ByteBuf;
use simple_asn1::ASN1EncodeErr;
use std::fmt;
const MAX_SIGS_TO_PRUNE: usize = 10;
type Nonce = String;
pub fn prepare_login(address: &SolPubkey) -> SiwsMessage {
let nonce = generate_nonce();
let message = SiwsMessage::new(address, &nonce);
SIWS_MESSAGES.with_borrow_mut(|siws_messages| {
siws_messages.insert(address, message.clone(), &nonce);
});
message
}
#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct LoginDetails {
pub expiration: u64,
pub user_canister_pubkey: ByteBuf,
}
pub enum LoginError {
SignatureError(SolError),
SiwsMessageError(SiwsMessageError),
AddressMismatch,
DelegationError(DelegationError),
ASN1EncodeErr(ASN1EncodeErr),
}
impl From<SolError> for LoginError {
fn from(err: SolError) -> Self {
LoginError::SignatureError(err)
}
}
impl From<SiwsMessageError> for LoginError {
fn from(err: SiwsMessageError) -> Self {
LoginError::SiwsMessageError(err)
}
}
impl From<DelegationError> for LoginError {
fn from(err: DelegationError) -> Self {
LoginError::DelegationError(err)
}
}
impl From<ASN1EncodeErr> for LoginError {
fn from(err: ASN1EncodeErr) -> Self {
LoginError::ASN1EncodeErr(err)
}
}
impl fmt::Display for LoginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LoginError::SignatureError(e) => write!(f, "{}", e),
LoginError::SiwsMessageError(e) => write!(f, "{}", e),
LoginError::AddressMismatch => write!(f, "Recovered address does not match"),
LoginError::DelegationError(e) => write!(f, "{}", e),
LoginError::ASN1EncodeErr(e) => write!(f, "{}", e),
}
}
}
pub fn login(
signature: &SolSignature,
address: &SolPubkey,
session_key: ByteBuf,
signature_map: &mut SignatureMap,
canister_id: &Principal,
nonce: &Nonce,
) -> Result<LoginDetails, LoginError> {
SIWS_MESSAGES.with_borrow_mut(|siws_messages| {
siws_messages.prune_expired();
let message = siws_messages.get(address, nonce)?;
let message_string: String = message.clone().into();
let verification_result = verify_sol_signature(&message_string, signature, address);
siws_messages.remove(address, nonce);
verification_result?;
let expiration = with_settings!(|settings: &Settings| {
message
.issued_at
.saturating_add(settings.session_expires_in)
});
let seed = generate_seed(address);
signature_map.prune_expired(get_current_time(), MAX_SIGS_TO_PRUNE);
let delegation = create_delegation(session_key, expiration)?;
let delegation_hash = create_delegation_hash(&delegation);
signature_map.put(hash::hash_bytes(seed), delegation_hash);
let user_canister_pubkey = create_user_canister_pubkey(canister_id, seed.to_vec())?;
Ok(LoginDetails {
expiration,
user_canister_pubkey: ByteBuf::from(user_canister_pubkey),
})
})
}