diff --git a/examples/federation_example.py b/examples/federation_example.py
index 8c2d0661af79511f7197bced024ff90d72511b69..84d0b59a8351011603f856791d9328770e3c9bd9 100644
--- a/examples/federation_example.py
+++ b/examples/federation_example.py
@@ -1,200 +1,115 @@
 """
-Federation examples showing common ActivityPub interactions.
+Federation example showing handlers and signatures.
 """
 
 import asyncio
 import logging
 from datetime import datetime, timezone
 from pathlib import Path
-import sys
 
-# Add src directory to Python path
-src_path = Path(__file__).parent.parent / "src"
-sys.path.insert(0, str(src_path))
-
-from pyfed.security.key_management import KeyManager
-from pyfed.federation.delivery import ActivityDelivery
-from pyfed.federation.discovery import InstanceDiscovery
+from pyfed.models import APCreate, APNote, APPerson, APLike
+from pyfed.security import KeyManager
+from pyfed.security.http_signatures import HTTPSignatureVerifier
+from pyfed.storage import StorageBackend
+from pyfed.federation import ActivityDelivery
+from pyfed.handlers import CreateHandler, LikeHandler
 from pyfed.protocols.webfinger import WebFingerClient
-from pyfed.models import APCreate, APNote, APPerson, APFollow, APLike, APAnnounce
 
-# Configure logging
-logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
-class FederationExample:
-    """Federation interaction examples."""
-
-    def __init__(self, domain: str = "example.com"):
-        self.domain = domain
-        self.key_manager = None
-        self.delivery = None
-        self.webfinger = None
-        self.discovery = None
-
-    async def initialize(self):
-        """Initialize components."""
-        # Initialize key manager
-        self.key_manager = KeyManager(
-            domain=self.domain,
-            keys_path=str(Path("example_keys").resolve())
+async def federation_example():
+    """Example of federation with handlers and signatures."""
+    try:
+        # Initialize components
+        storage = StorageBackend.create(
+            provider="sqlite",
+            database_url="example_data/pyfed_example.db"
         )
-        logger.info("Initializing key manager...")
-        await self.key_manager.initialize()
+        await storage.initialize()
 
-        # Initialize delivery
-        self.delivery = ActivityDelivery(
-            key_manager=self.key_manager,
-            timeout=30
+        # Initialize key manager and signature verifier
+        key_manager = KeyManager(
+            domain="example.com",
+            private_key_path="example_keys/private.pem",
+            public_key_path="example_keys/public.pem"
         )
-        logger.info("Initializing delivery...")
-        await self.delivery.initialize()
+        await key_manager.initialize()
 
-        # Initialize WebFinger client with SSL verification disabled for testing
-        self.webfinger = WebFingerClient(verify_ssl=False)
-        logger.info("Initializing WebFinger client...")
-        await self.webfinger.initialize()
+        signature_verifier = HTTPSignatureVerifier(key_manager)
 
-    async def send_public_post(self, content: str):
-        """Send a public post to the Fediverse."""
-        logger.info(f"Sending public post: {content}")
+        # Initialize handlers
+        create_handler = CreateHandler(storage)
+        like_handler = LikeHandler(storage)
+
+        # Initialize delivery with signature support
+        delivery = ActivityDelivery(
+            key_manager=key_manager,
+            signature_verifier=signature_verifier
+        )
 
-        # Create local actor
+        # Create and send a note
         actor = APPerson(
-            id=f"https://{self.domain}/users/alice",
+            id="https://example.com/users/alice",
             name="Alice",
             preferred_username="alice",
-            inbox=f"https://{self.domain}/users/alice/inbox",
-            outbox=f"https://{self.domain}/users/alice/outbox"
+            inbox="https://example.com/users/alice/inbox",
+            outbox="https://example.com/users/alice/outbox"
         )
 
-        # Create note
         note = APNote(
-            id=f"https://{self.domain}/notes/{datetime.utcnow().timestamp()}",
-            content=content,
+            id=f"https://example.com/notes/{datetime.now(timezone.utc).timestamp()}",
+            content="Hello Fediverse!",
             attributed_to=str(actor.id),
-            to=["https://www.w3.org/ns/activitystreams#Public"],
-            published=datetime.utcnow().isoformat()
+            to=["https://www.w3.org/ns/activitystreams#Public"]
         )
 
         # Create activity
         create_activity = APCreate(
-            id=f"https://{self.domain}/activities/{datetime.utcnow().timestamp()}",
+            id=f"https://example.com/activities/{datetime.now(timezone.utc).timestamp()}",
             actor=str(actor.id),
             object=note,
-            to=note.to,
-            published=datetime.utcnow().isoformat()
-        )
-
-        # Deliver to followers (example)
-        activity_dict = create_activity.serialize()
-        result = await self.delivery.deliver_activity(
-            activity=activity_dict,
-            recipients=[f"https://{self.domain}/followers"]
+            to=note.to
         )
-        logger.info(f"Delivery result: {result}")
-
-    async def send_direct_message(self, recipient: str, content: str):
-        """Send a direct message to a specific user."""
-        logger.info(f"Sending direct message to {recipient}")
 
-        # Resolve recipient's inbox
-        inbox_url = await self.webfinger.get_inbox_url(recipient)
-        if not inbox_url:
-            logger.error(f"Could not find inbox for {recipient}")
-            return
+        # Process outgoing activity through handler
+        await create_handler.handle_outgoing(create_activity.serialize())
 
-        # Create note
-        note = APNote(
-            id=f"https://{self.domain}/notes/{datetime.utcnow().timestamp()}",
-            content=content,
-            attributed_to=f"https://{self.domain}/users/alice",
-            to=[inbox_url],
-            published=datetime.utcnow().isoformat()
-        )
-
-        # Create activity
-        create_activity = APCreate(
-            id=f"https://{self.domain}/activities/{datetime.utcnow().timestamp()}",
-            actor=f"https://{self.domain}/users/alice",
-            object=note,
-            to=note.to,
-            published=datetime.utcnow().isoformat()
-        )
-
-        # Deliver direct message
+        # Sign and deliver activity
         activity_dict = create_activity.serialize()
-        result = await self.delivery.deliver_activity(
-            activity=activity_dict,
-            recipients=[inbox_url]
+        signed_headers = await signature_verifier.sign_request(
+            method="POST",
+            path="/inbox",
+            body=activity_dict
         )
-        logger.info(f"Delivery result: {result}")
-
-    async def follow_account(self, account: str):
-        """Follow a remote account."""
-        logger.info(f"Following account: {account}")
-
-        # Resolve account
-        actor_url = await self.webfinger.get_actor_url(account)
-        if not actor_url:
-            logger.error(f"Could not resolve account {account}")
-            return
-
-        # Create Follow activity
-        follow = APFollow(
-            id=f"https://{self.domain}/activities/follow_{datetime.utcnow().timestamp()}",
-            actor=f"https://{self.domain}/users/alice",
-            object=actor_url,
-            published=datetime.utcnow().isoformat()
-        )
-
-        # Get target inbox
-        inbox_url = await self.webfinger.get_inbox_url(account)
-        if not inbox_url:
-            logger.error(f"Could not find inbox for {account}")
-            return
 
-        # Deliver Follow activity
-        activity_dict = follow.serialize()
-        result = await self.delivery.deliver_activity(
+        # Deliver with signed headers
+        result = await delivery.deliver_activity(
             activity=activity_dict,
-            recipients=[inbox_url]
-        )
-        logger.info(f"Follow result: {result}")
-
-    async def close(self):
-        """Clean up resources."""
-        if self.delivery and hasattr(self.delivery, 'close'):
-            await self.delivery.close()
-        if self.webfinger and hasattr(self.webfinger, 'close'):
-            await self.webfinger.close()
-
-async def main():
-    """Run federation examples."""
-    federation = FederationExample()
-    logger.info("Initializing federation...")
-    await federation.initialize()
-
-    try:
-        # Example 1: Send public post
-        logger.info("Sending public post...")
-        await federation.send_public_post(
-            "Hello #Fediverse! This is a test post from PyFed!"
-        )
-
-        # Example 2: Send direct message
-        logger.info("Sending direct message...")
-        await federation.send_direct_message(
-            "kene29@mastodon.social",
-            "Hello! This is a direct message test from PyFed."
+            recipients=["https://remote.example/inbox"],
+            headers=signed_headers
         )
 
-        # Example 3: Follow account
-        logger.info("Following account...")
-        await federation.follow_account("kene29@mastodon.social")
+        # Simulate receiving an activity
+        incoming_headers = {
+            "Signature": "...",  # Signature from remote server
+            "Date": "...",
+            "Host": "example.com"
+        }
+        incoming_activity = {
+            "type": "Like",
+            "actor": "https://remote.example/users/bob",
+            "object": note.id
+        }
+
+        # Verify signature of incoming activity
+        if await signature_verifier.verify_request(incoming_headers):
+            # Process through handler
+            await like_handler.handle_incoming(incoming_activity)
+        else:
+            logger.error("Invalid signature on incoming activity")
 
     finally:
-        await federation.close()
+        await storage.close()
 
 if __name__ == "__main__":
-    asyncio.run(main()) 
\ No newline at end of file
+    asyncio.run(federation_example()) 
\ No newline at end of file
diff --git a/examples/note_example.py b/examples/note_example.py
index c966d54594fc4141f92563910cf5459e5a5b5d6e..714d8708fa43c590ae506b7e01d8d1b09fed6b92 100644
--- a/examples/note_example.py
+++ b/examples/note_example.py
@@ -9,6 +9,7 @@ import os
 import sys
 from pathlib import Path
 import ssl
+from urllib.parse import urlparse
 
 # Add src directory to Python path
 src_path = Path(__file__).parent.parent / "src"
@@ -19,6 +20,7 @@ from pyfed.security import KeyManager
 from pyfed.storage import StorageBackend
 from pyfed.federation import ActivityDelivery
 from pyfed.protocols.webfinger import WebFingerClient
+from pyfed.security.http_signatures import HTTPSignatureVerifier
 from pyfed.serializers.json_serializer import to_json, from_json
 
 # Configure logging
@@ -58,6 +60,16 @@ async def create_note_example():
         )
         await key_manager.initialize()
 
+        # Get the active key for signing
+        active_key = await key_manager.get_active_key()
+        logger.info(f"Active key: {active_key}")
+
+        # Initialize HTTP signature verifier with the active key paths
+        signature_verifier = HTTPSignatureVerifier(
+            private_key_path=f"{key_manager.keys_path}/{active_key.key_id}_private.pem",
+            public_key_path=f"{key_manager.keys_path}/{active_key.key_id}_public.pem"
+        )
+
         # Initialize delivery
         delivery = ActivityDelivery(
             key_manager=key_manager,
@@ -112,13 +124,25 @@ async def create_note_example():
         # Serialize and deliver
         logger.info("Serializing activity...")
         activity_dict = create_activity.serialize()
-        logger.info(f"Serialized activity: {activity_dict}")
-        logger.info(f"Activity: {to_json(create_activity, indent=2)}")
+        # logger.info(f"Serialized activity: {activity_dict}")
+        # logger.info(f"Activity: {to_json(create_activity, indent=2)}")
+
+        # Sign the request
+        logger.debug(f"Resolved host: {urlparse(inbox_url).netloc}")
+        signed_headers = await signature_verifier.sign_request(
+            method="POST",
+            path="/inbox",
+            headers={"Host": urlparse(inbox_url).netloc},
+            body=activity_dict
+        )
+
+        logger.debug(f"Headers before signing: {signed_headers}")
 
         logger.info("Delivering activity...")
         result = await delivery.deliver_activity(
             activity=activity_dict,
-            recipients=[inbox_url]
+            recipients=[inbox_url],
+            headers=signed_headers  # Include signed headers
         )
         logger.info(f"Delivery result: {result}")
 
diff --git a/src/pyfed/.DS_Store b/src/pyfed/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..c6932cc849686664d20de6776ac3859c5ece9817
Binary files /dev/null and b/src/pyfed/.DS_Store differ
diff --git a/src/pyfed/federation/resolver.py b/src/pyfed/federation/resolver.py
index a66a31febdabeff278bcab8e8b3b390a70c0d092..6dda1ddcee695c5e191e21c2b52c71ec1e734a11 100644
--- a/src/pyfed/federation/resolver.py
+++ b/src/pyfed/federation/resolver.py
@@ -10,7 +10,7 @@ from urllib.parse import urlparse
 from ..utils.exceptions import ResolverError
 from ..utils.logging import get_logger
 from ..cache.actor_cache import ActorCache
-from ..security.webfinger import WebFingerService
+from ..protocols.webfinger import WebFingerClient
 
 logger = get_logger(__name__)
 
@@ -19,7 +19,7 @@ class ActivityPubResolver:
 
     def __init__(self, 
                  actor_cache: Optional[ActorCache] = None,
-                 discovery_service: Optional[WebFingerService] = None):
+                 discovery_service: Optional[WebFingerClient] = None):
         """
         Initialize resolver.
         
diff --git a/src/pyfed/protocols/webfinger.py b/src/pyfed/protocols/webfinger.py
index 32697e5c4de8d4d437926a5ab11299560ae0b90c..7dcd164d0380f76603388bb53fa7491dfd0b7a56 100644
--- a/src/pyfed/protocols/webfinger.py
+++ b/src/pyfed/protocols/webfinger.py
@@ -5,35 +5,23 @@ WebFinger protocol implementation.
 from typing import Dict, Any, Optional
 import aiohttp
 from urllib.parse import quote
-import json
+import logging
 
-from ..utils.exceptions import WebFingerError
-from ..utils.logging import get_logger
-
-logger = get_logger(__name__)
+logger = logging.getLogger(__name__)
 
 class WebFingerClient:
     """WebFinger client implementation."""
 
-    def __init__(self, timeout: int = 30, verify_ssl: bool = True):
-        self.timeout = timeout
+    def __init__(self, verify_ssl: bool = True):
         self.verify_ssl = verify_ssl
         self.session = None
 
     async def initialize(self) -> None:
         """Initialize client."""
-        # Create SSL context
-        ssl_context = None
-        if not self.verify_ssl:
-            import ssl
-            ssl_context = ssl.create_default_context()
-            ssl_context.check_hostname = False
-            ssl_context.verify_mode = ssl.CERT_NONE
-
+        ssl = None if self.verify_ssl else False
         self.session = aiohttp.ClientSession(
-            timeout=aiohttp.ClientTimeout(total=self.timeout),
             headers={"Accept": "application/jrd+json, application/json"},
-            connector=aiohttp.TCPConnector(ssl=ssl_context)
+            connector=aiohttp.TCPConnector(ssl=ssl)
         )
 
     async def finger(self, account: str) -> Optional[Dict[str, Any]]:
@@ -41,34 +29,27 @@ class WebFingerClient:
         Perform WebFinger lookup.
         
         Args:
-            account: Account to look up (e.g. user@domain.com)
+            account: Account to look up (e.g., user@domain.com)
             
         Returns:
             WebFinger response if found
         """
         try:
-            # Parse account
-            if account.startswith('acct:'):
-                account = account[5:]
-            if '@' not in account:
-                raise WebFingerError(f"Invalid account format: {account}")
-
-            username, domain = account.split('@')
-            resource = f"acct:{username}@{domain}"
-
-            # Construct WebFinger URL
-            url = f"https://{domain}/.well-known/webfinger?resource={quote(resource)}"
-
-            # Perform lookup
-            async with self.session.get(url) as response:
-                if response.status != 200:
-                    logger.error(
-                        f"WebFinger lookup failed for {account}: {response.status}"
-                    )
-                    return None
+            if not '@' in account:
+                return None
+
+            # Ensure acct: prefix
+            if not account.startswith('acct:'):
+                account = f"acct:{account}"
 
-                data = await response.json()
-                return data
+            domain = account.split('@')[-1]
+            url = f"https://{domain}/.well-known/webfinger?resource={quote(account)}"
+
+            response = await self.session.get(url)
+            async with response as resp:
+                if resp.status != 200:
+                    return None
+                return await resp.json()
 
         except Exception as e:
             logger.error(f"WebFinger lookup failed for {account}: {e}")
@@ -89,14 +70,10 @@ class WebFingerClient:
             if not data:
                 return None
 
-            # Look for actor URL in links
             for link in data.get('links', []):
-                if (
-                    link.get('rel') == 'self' and
-                    link.get('type') == 'application/activity+json'
-                ):
+                if (link.get('rel') == 'self' and 
+                    link.get('type') == 'application/activity+json'):
                     return link.get('href')
-
             return None
 
         except Exception as e:
@@ -114,18 +91,17 @@ class WebFingerClient:
             Inbox URL if found
         """
         try:
-            # First get actor URL
             actor_url = await self.get_actor_url(account)
+            logger.debug(f"actor_url: {actor_url}")
             if not actor_url:
                 return None
 
-            # Fetch actor object
-            async with self.session.get(actor_url) as response:
-                if response.status != 200:
+            response = await self.session.get(actor_url)
+            async with response as resp:
+                if resp.status != 200:
                     return None
-
-                actor = await response.json()
-                return actor.get('inbox')
+                data = await resp.json()
+                return data.get('inbox')
 
         except Exception as e:
             logger.error(f"Failed to get inbox URL for {account}: {e}")
diff --git a/src/pyfed/security/interfaces.py b/src/pyfed/security/interfaces.py
deleted file mode 100644
index bc34d471cc8989246c34d934fc7cf0b94fbe92fb..0000000000000000000000000000000000000000
--- a/src/pyfed/security/interfaces.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-Security component interfaces.
-"""
-
-from abc import ABC, abstractmethod
-from typing import Dict, Any, Optional
-
-class SignatureVerifier(ABC):
-    """Interface for HTTP signature verification."""
-    
-    @abstractmethod
-    async def verify_request(self, headers: Dict[str, str]) -> bool:
-        """
-        Verify request signature.
-        
-        Args:
-            headers: Request headers including signature
-            
-        Returns:
-            True if signature is valid
-        """
-        pass
-    
-    @abstractmethod
-    async def sign_request(self, 
-                          method: str,
-                          path: str,
-                          host: str,
-                          body: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
-        """
-        Sign a request.
-        
-        Args:
-            method: HTTP method
-            path: Request path
-            host: Target host
-            body: Optional request body
-            
-        Returns:
-            Headers with signature
-            
-        Raises:
-            SignatureError: If signing fails
-        """
-        pass 
\ No newline at end of file
diff --git a/src/pyfed/security/rate_limiter.py b/src/pyfed/security/rate_limiter.py
deleted file mode 100644
index de848fca5a480672a0dbce328d760fdc98902cb1..0000000000000000000000000000000000000000
--- a/src/pyfed/security/rate_limiter.py
+++ /dev/null
@@ -1,158 +0,0 @@
-"""
-Enhanced rate limiting with better configurability and monitoring.
-"""
-
-from typing import Dict, Optional, NamedTuple, List
-import time
-import asyncio
-from datetime import datetime
-from dataclasses import dataclass
-import redis.asyncio as redis
-from ..utils.exceptions import RateLimitExceeded
-
-from ..utils.logging import get_logger
-
-logger = get_logger(__name__)
-
-@dataclass
-class RateLimitConfig:
-    """Rate limit configuration."""
-    requests_per_minute: int
-    window_seconds: int = 60
-    burst_size: int = 0  # Allow burst over limit
-    redis_url: Optional[str] = None
-
-class RateLimitInfo(NamedTuple):
-    """Rate limit information."""
-    remaining: int
-    reset_time: int
-    limit: int
-
-class RateLimiter:
-    """Enhanced rate limiting implementation."""
-    
-    def __init__(self, config: RateLimitConfig):
-        self.config = config
-        self.redis_client = None
-        self._local_counters: Dict[str, Dict] = {}
-        self._lock = asyncio.Lock()
-        self.metrics = {
-            'total_requests': 0,
-            'exceeded_limits': 0,
-            'redis_errors': 0
-        }
-
-    async def init(self) -> None:
-        """Initialize rate limiter."""
-        if self.config.redis_url:
-            await self._init_redis()
-
-    async def _init_redis(self) -> None:
-        """Initialize Redis connection with retries."""
-        retries = 3
-        for attempt in range(retries):
-            try:
-                self.redis_client = redis.from_url(self.config.redis_url)
-                await self.redis_client.ping()
-                break
-            except Exception as e:
-                if attempt == retries - 1:
-                    logger.error(f"Failed to connect to Redis: {e}")
-                    self.redis_client = None
-                else:
-                    await asyncio.sleep(1)
-
-    async def check_rate_limit(self, client_id: str) -> RateLimitInfo:
-        """Enhanced rate limit check with detailed information."""
-        self.metrics['total_requests'] += 1
-        try:
-            current_time = int(time.time())
-            
-            if self.redis_client:
-                count = await self._check_redis_rate_limit(client_id, current_time)
-            else:
-                async with self._lock:
-                    count = await self._check_local_rate_limit(client_id, current_time)
-            
-            remaining = max(0, self.config.requests_per_minute - count)
-            reset_time = (current_time // self.config.window_seconds + 1) * self.config.window_seconds
-            
-            if count > self.config.requests_per_minute + self.config.burst_size:
-                self.metrics['exceeded_limits'] += 1
-                raise RateLimitExceeded(
-                    message=f"Rate limit exceeded. Try again after {reset_time}",
-                    reset_time=reset_time,
-                    limit=self.config.requests_per_minute,
-                    remaining=remaining
-                )
-            
-            return RateLimitInfo(
-                remaining=remaining,
-                reset_time=reset_time,
-                limit=self.config.requests_per_minute
-            )
-            
-        except RateLimitExceeded:
-            raise
-        except redis.RedisError as e:
-            self.metrics['redis_errors'] += 1
-            logger.error(f"Redis error in rate limit check: {e}")
-            # Fall back to local rate limiting
-            return await self._check_local_rate_limit(client_id, current_time)
-
-    async def _check_redis_rate_limit(self, client_id: str, current_time: int) -> int:
-        """Enhanced Redis rate limiting with sliding window."""
-        key = f"rate_limit:{client_id}"
-        window_key = f"{key}:{current_time // self.config.window_seconds}"
-        
-        async with self.redis_client.pipeline() as pipe:
-            try:
-                # Use pipeline for atomic operations
-                pipe.watch(window_key)
-                current_count = await pipe.get(window_key) or 0
-                
-                pipe.multi()
-                pipe.incr(window_key)
-                pipe.expire(window_key, self.config.window_seconds)
-                
-                # Handle sliding window
-                prev_window_key = f"{key}:{(current_time // self.config.window_seconds) - 1}"
-                prev_count = await pipe.get(prev_window_key) or 0
-                
-                results = await pipe.execute()
-                current_count = int(results[0])
-                
-                # Calculate weighted count for sliding window
-                weight = ((current_time % self.config.window_seconds) / self.config.window_seconds)
-                total_count = int(current_count + (int(prev_count) * (1 - weight)))
-                
-                return total_count
-                
-            except redis.WatchError:
-                # Key modified, retry
-                return await self._check_redis_rate_limit(client_id, current_time)
-
-    async def get_metrics(self) -> Dict[str, int]:
-        """Get rate limiter metrics."""
-        return {
-            **self.metrics,
-            'active_clients': len(self._local_counters)
-        }
-
-    async def reset_limits(self, client_id: Optional[str] = None) -> None:
-        """Reset rate limits for client or all clients."""
-        if client_id:
-            if self.redis_client:
-                pattern = f"rate_limit:{client_id}:*"
-                keys = await self.redis_client.keys(pattern)
-                if keys:
-                    await self.redis_client.delete(*keys)
-            if client_id in self._local_counters:
-                del self._local_counters[client_id]
-        else:
-            if self.redis_client:
-                pattern = "rate_limit:*"
-                keys = await self.redis_client.keys(pattern)
-                if keys:
-                    await self.redis_client.delete(*keys)
-            self._local_counters.clear()
\ No newline at end of file
diff --git a/src/pyfed/security/validators.py b/src/pyfed/security/validators.py
deleted file mode 100644
index 0519ecba6ea913e21689ec692e81e9e4973fbf73..0000000000000000000000000000000000000000
--- a/src/pyfed/security/validators.py
+++ /dev/null
@@ -1 +0,0 @@
- 
\ No newline at end of file
diff --git a/src/pyfed/security/webfinger.py b/src/pyfed/security/webfinger.py
deleted file mode 100644
index e0f2ca91b107df4c5355888ae1ef6b94c720c667..0000000000000000000000000000000000000000
--- a/src/pyfed/security/webfinger.py
+++ /dev/null
@@ -1,263 +0,0 @@
-"""
-Enhanced WebFinger implementation for ActivityPub.
-"""
-
-import aiohttp
-import json
-from typing import Dict, Any, Optional, List, Union
-from dataclasses import dataclass
-from urllib.parse import urlparse, urljoin
-import cachetools
-import asyncio
-from datetime import timedelta
-
-@dataclass
-class WebFingerConfig:
-    """Configuration for WebFinger service."""
-    cache_ttl: int = 3600  # Cache TTL in seconds
-    timeout: int = 10      # Request timeout in seconds
-    max_redirects: int = 3
-    cache_size: int = 1000
-    user_agent: str = "PyFed/1.0 (WebFinger)"
-    allowed_protocols: List[str] = ("https",)
-
-class WebFingerError(Exception):
-    """Base exception for WebFinger errors."""
-    pass
-
-class WebFingerService:
-    """Enhanced WebFinger service."""
-
-    def __init__(self, 
-                local_domain: str,
-                config: Optional[WebFingerConfig] = None):
-        """
-        Initialize WebFinger service.
-        
-        Args:
-            local_domain: Local server domain
-            config: Optional configuration
-        """
-        self.local_domain = local_domain
-        self.config = config or WebFingerConfig()
-        
-        # Initialize cache
-        self.cache = cachetools.TTLCache(
-            maxsize=self.config.cache_size,
-            ttl=self.config.cache_ttl
-        )
-        
-        # Lock for thread safety
-        self._lock = asyncio.Lock()
-        
-        # Metrics
-        self.metrics = {
-            'webfinger_requests': 0,
-            'cache_hits': 0,
-            'cache_misses': 0,
-            'failed_requests': 0
-        }
-
-    async def get_resource(self, resource: str) -> Dict[str, Any]:
-        """
-        Get WebFinger resource data.
-        
-        Args:
-            resource: Resource identifier (acct:user@domain or https://domain/users/user)
-            
-        Returns:
-            Dict containing resource data
-            
-        Raises:
-            WebFingerError: If resource lookup fails
-        """
-        try:
-            self.metrics['webfinger_requests'] += 1
-            
-            # Check cache
-            async with self._lock:
-                if resource in self.cache:
-                    self.metrics['cache_hits'] += 1
-                    return self.cache[resource]
-                self.metrics['cache_misses'] += 1
-
-            # Parse resource
-            domain = self._get_domain(resource)
-            if not domain:
-                raise WebFingerError(f"Invalid resource: {resource}")
-
-            # Make WebFinger request
-            data = await self._fetch_webfinger(domain, resource)
-            
-            # Cache result
-            async with self._lock:
-                self.cache[resource] = data
-                
-            return data
-            
-        except WebFingerError:
-            raise
-        except Exception as e:
-            self.metrics['failed_requests'] += 1
-            raise WebFingerError(f"WebFinger lookup failed: {e}")
-
-    async def get_actor_url(self, account: str) -> str:
-        """
-        Get ActivityPub actor URL for account.
-        
-        Args:
-            account: Account in format user@domain
-            
-        Returns:
-            Actor URL
-            
-        Raises:
-            WebFingerError: If actor lookup fails
-        """
-        try:
-            # Validate account format
-            if '@' not in account:
-                raise WebFingerError("Invalid account format")
-
-            username, domain = account.split('@')
-            
-            # First try WebFinger
-            resource = f"acct:{account}"
-            try:
-                data = await self.get_resource(resource)
-                
-                # Look for ActivityPub profile URL
-                for link in data.get('links', []):
-                    if (link.get('rel') == 'self' and 
-                        link.get('type') == 'application/activity+json'):
-                        return link['href']
-                        
-            except WebFingerError:
-                # Fall back to direct URL only for allowed protocols
-                if domain:
-                    url = f"https://{domain}/users/{username}"
-                    if urlparse(url).scheme in self.config.allowed_protocols:
-                        return url
-                        
-            raise WebFingerError(f"Could not determine actor URL for {account}")
-            
-        except WebFingerError:
-            raise
-        except Exception as e:
-            raise WebFingerError(f"Actor URL lookup failed: {e}")
-
-    async def get_actor_data(self, url: str) -> Dict[str, Any]:
-        """
-        Fetch actor data from URL.
-        
-        Args:
-            url: Actor URL
-            
-        Returns:
-            Actor data
-            
-        Raises:
-            WebFingerError: If actor fetch fails
-        """
-        try:
-            headers = {
-                "Accept": "application/activity+json",
-                "User-Agent": self.config.user_agent
-            }
-            
-            timeout = aiohttp.ClientTimeout(total=self.config.timeout)
-            
-            async with aiohttp.ClientSession(timeout=timeout) as session:
-                async with session.get(url, headers=headers) as response:
-                    if response.status != 200:
-                        raise WebFingerError(
-                            f"Failed to fetch actor: {response.status}"
-                        )
-                    
-                    data = await response.json()
-                    
-                    # Validate data
-                    if not isinstance(data, dict):
-                        raise WebFingerError("Invalid actor data format")
-                        
-                    required_fields = {'id', 'type'}
-                    if not all(field in data for field in required_fields):
-                        raise WebFingerError("Missing required actor fields")
-                        
-                    return data
-
-        except aiohttp.ClientError as e:
-            raise WebFingerError(f"Network error: {e}")
-        except json.JSONDecodeError as e:
-            raise WebFingerError(f"Invalid JSON response: {e}")
-        except Exception as e:
-            raise WebFingerError(f"Failed to fetch actor data: {e}")
-
-    async def _fetch_webfinger(self, domain: str, resource: str) -> Dict[str, Any]:
-        """Make WebFinger request with security checks."""
-        try:
-            # Validate domain and build URL
-            if not self._is_valid_domain(domain):
-                raise WebFingerError(f"Invalid domain: {domain}")
-                
-            webfinger_url = f"https://{domain}/.well-known/webfinger"
-            params = {"resource": resource}
-            
-            headers = {
-                "Accept": "application/jrd+json",
-                "User-Agent": self.config.user_agent
-            }
-            
-            timeout = aiohttp.ClientTimeout(total=self.config.timeout)
-            
-            async with aiohttp.ClientSession(timeout=timeout) as session:
-                async with session.get(
-                    webfinger_url,
-                    params=params,
-                    headers=headers,
-                    max_redirects=self.config.max_redirects
-                ) as response:
-                    if response.status != 200:
-                        raise WebFingerError(
-                            f"WebFinger request failed: {response.status}"
-                        )
-                    
-                    data = await response.json()
-                    
-                    # Validate response
-                    if not isinstance(data, dict):
-                        raise WebFingerError("Invalid WebFinger response format")
-                        
-                    if 'subject' not in data:
-                        raise WebFingerError("Missing required field: subject")
-                        
-                    return data
-                    
-        except Exception as e:
-            raise WebFingerError(f"WebFinger request failed: {e}")
-
-    def _get_domain(self, resource: str) -> Optional[str]:
-        """Extract domain from resource with validation."""
-        try:
-            if resource.startswith('acct:'):
-                _, address = resource.split(':', 1)
-                if '@' in address:
-                    return address.split('@')[1]
-            else:
-                parsed = urlparse(resource)
-                if parsed.netloc:
-                    return parsed.netloc
-            return None
-        except Exception:
-            return None
-
-    def _is_valid_domain(self, domain: str) -> bool:
-        """Validate domain name."""
-        if not domain:
-            return False
-        # Add your domain validation logic here
-        return True
-
-    async def cleanup(self):
-        """Cleanup resources."""
-        self.cache.clear()
\ No newline at end of file
diff --git a/tests/pytest.ini b/tests/pytest.ini
index 138df0988d74e9648ba05623b89a4f7c53d80fae..f9025ee838d335d198b489e7d813f21147dd0e62 100644
--- a/tests/pytest.ini
+++ b/tests/pytest.ini
@@ -1,4 +1,5 @@
 [pytest]  
+asyncio_mode = auto
 filterwarnings =  
     ignore::DeprecationWarning
 pythonpath = ../src
\ No newline at end of file