import asyncio import secrets import unittest from fastmcp.experimental.auth.atproto.provider import _SignInError from joserfc.errors import JoseError from home.auth import DIDOnlyATProtoProvider DID = "did:plc:xbtmt2zjwlrfegqvch7fboei" class Auth(unittest.IsolatedAsyncioTestCase): def provider(self): return DIDOnlyATProtoProvider( base_url="https://agents.example.test", jwt_signing_key=secrets.token_urlsafe(48), allowed_dids=[DID], require_authorization_consent=True, ) async def test_unapproved_identifier_cannot_connect_to_loopback(self): connections = [] async def connected(reader, writer): connections.append(True) writer.close() await writer.wait_closed() server = await asyncio.start_server(connected, "127.0.0.1", 0) try: port = server.sockets[0].getsockname()[1] with self.assertRaises(_SignInError): await self.provider()._start_authorization("test", f"127.0.0.1:{port}") await asyncio.sleep(0.05) self.assertEqual(connections, []) finally: server.close() await server.wait_closed() async def test_unapproved_did_rejected(self): with self.assertRaises(_SignInError): await self.provider()._start_authorization("test", "did:plc:unapproved") async def test_malformed_access_token_rejected(self): self.assertIsNone(await self.provider().load_access_token("not-a-token")) def test_identity_token_audience_and_expiry(self): provider = self.provider() issuer = provider._identity_issuer token = issuer.issue_access_token( client_id="test", scopes=[], jti="test", expires_in=-10 ) with self.assertRaises(JoseError): issuer.verify_token(token) other = self.provider()._identity_issuer token = issuer.issue_access_token(client_id="test", scopes=[], jti="test") with self.assertRaises(JoseError): other.verify_token(token)