- platform/interface.py: PlatformClient Protocol + Pydantic models (User, MessageResponse, UserSettings) — no explicit session management, Master handles container lifecycle - platform/mock.py: MockPlatformClient with simulated latency, [MOCK] responses, is_new correctly True only on first creation - core/protocol.py: unified dataclasses for all events and responses (IncomingMessage/Command/Callback, OutgoingMessage/UI/Notification, AuthFlow, ChatContext, SettingsAction, etc.) - core/store.py: StateStore Protocol + InMemoryStore (tests) + SQLiteStore (prod) with JSON serialization - core/chat.py: ChatManager — chat metadata (C1/C2/C3), not container lifecycle (that's the platform's job) - core/auth.py: AuthManager — start_flow / confirm / is_authenticated - core/settings.py: SettingsManager — get/apply with store cache - core/handler.py: EventDispatcher — registry-based routing with keys (command name, action name, attachment type, "*" catch-all) - core/handlers/: register_all() + start/new/message/callback/settings handlers; voice slot falls back to stub text until voice_handler added - conftest.py: sys.path fix so local platform/ shadows stdlib platform - docs/api-contract.md: rewritten for Lambda Lab 3.0 container model 46 tests passing, 0 warnings.
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
# core/auth.py
|
||
from __future__ import annotations
|
||
|
||
import structlog
|
||
|
||
from core.protocol import AuthFlow
|
||
from core.store import StateStore
|
||
|
||
logger = structlog.get_logger(__name__)
|
||
|
||
|
||
def _to_dict(flow: AuthFlow) -> dict:
|
||
return {
|
||
"user_id": flow.user_id,
|
||
"platform": flow.platform,
|
||
"state": flow.state,
|
||
"platform_user_id": flow.platform_user_id,
|
||
}
|
||
|
||
|
||
def _from_dict(d: dict) -> AuthFlow:
|
||
return AuthFlow(
|
||
user_id=d["user_id"],
|
||
platform=d["platform"],
|
||
state=d["state"],
|
||
platform_user_id=d.get("platform_user_id"),
|
||
)
|
||
|
||
|
||
class AuthManager:
|
||
def __init__(self, platform: object, store: StateStore) -> None:
|
||
self._store = store
|
||
|
||
async def start_flow(self, user_id: str, platform: str) -> AuthFlow:
|
||
flow = AuthFlow(user_id=user_id, platform=platform, state="pending")
|
||
await self._store.set(f"auth:{user_id}", _to_dict(flow))
|
||
return flow
|
||
|
||
async def confirm(self, user_id: str) -> AuthFlow:
|
||
"""В моке — автоматическое подтверждение. В реальном SDK — валидация кода."""
|
||
stored = await self._store.get(f"auth:{user_id}")
|
||
if not stored:
|
||
stored = {"user_id": user_id, "platform": "unknown", "state": "pending", "platform_user_id": None}
|
||
|
||
stored["state"] = "confirmed"
|
||
stored["platform_user_id"] = f"plt_{user_id}"
|
||
await self._store.set(f"auth:{user_id}", stored)
|
||
return _from_dict(stored)
|
||
|
||
async def is_authenticated(self, user_id: str) -> bool:
|
||
stored = await self._store.get(f"auth:{user_id}")
|
||
return stored is not None and stored.get("state") == "confirmed"
|