use crate::error::{KrakyError, Result};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
#[derive(Clone)]
pub struct Credentials {
pub api_key: String,
api_secret: String,
}
impl Credentials {
pub fn new(api_key: impl Into<String>, api_secret: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
api_secret: api_secret.into(),
}
}
pub fn generate_token(&self, nonce: u64) -> Result<String> {
let secret_bytes = BASE64
.decode(&self.api_secret)
.map_err(|e| KrakyError::InvalidMessage(format!("Invalid API secret: {}", e)))?;
let message = nonce.to_string();
let mut mac = HmacSha256::new_from_slice(&secret_bytes)
.map_err(|e| KrakyError::InvalidMessage(format!("HMAC error: {}", e)))?;
mac.update(message.as_bytes());
let signature = mac.finalize().into_bytes();
Ok(BASE64.encode(signature))
}
pub fn api_key(&self) -> &str {
&self.api_key
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_credentials_creation() {
let creds = Credentials::new("test_key", "dGVzdF9zZWNyZXQ=");
assert_eq!(creds.api_key(), "test_key");
}
#[test]
fn test_token_generation() {
let creds = Credentials::new("test_key", "dGVzdF9zZWNyZXQ=");
let token = creds.generate_token(1234567890);
assert!(token.is_ok());
assert!(!token.unwrap().is_empty());
}
#[test]
fn test_invalid_secret() {
let creds = Credentials::new("test_key", "not-valid-base64!");
let token = creds.generate_token(1234567890);
assert!(token.is_err());
}
#[test]
fn test_deterministic_signing() {
let creds = Credentials::new("test_key", "dGVzdF9zZWNyZXQ=");
let token1 = creds.generate_token(1234567890).unwrap();
let token2 = creds.generate_token(1234567890).unwrap();
assert_eq!(token1, token2);
}
#[test]
fn test_different_nonces() {
let creds = Credentials::new("test_key", "dGVzdF9zZWNyZXQ=");
let token1 = creds.generate_token(1234567890).unwrap();
let token2 = creds.generate_token(9876543210).unwrap();
assert_ne!(token1, token2);
}
}