A second channel to the same dialog engine as the Telegram bot: consent, onboarding, scales and check-ins come from app/dialog; the bot is unchanged. - API: /auth/emias (doctor's one-time connect code via EmiasMock), /auth/emias/demo (synthetic personas), /auth/anonymous (random token, nothing stored in the identity DB), /crisis/resources from crisis.yaml - web/: React + TS + Vite, mobile-first, safe areas, hash routing, tokens and texts isolated for a Capacitor mobile build Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
"""ЕМИАС sign-in mock for the patient web app (SPEC §9 `esia_mock`: registration by the doctor's code).
|
|
|
|
Real ЕМИАС / ЕСИА OAuth needs a registered information system, so the prototype accepts the one-time connect
|
|
code the doctor hands out after the visit (323-FZ art. 36.2): code → patient token + attending doctor. The
|
|
class implements `IdentityProvider`, so the real OAuth client replaces it without touching the web channel.
|
|
|
|
It is deliberately NOT installed into `integrations.identity`: the Telegram channel verifies a code twice
|
|
(on `/start` and in the core flow), which a one-time redeeming provider would break. Lane B wires the
|
|
production provider for all channels (#14).
|
|
|
|
This module may use the identity DB: it is the registration boundary (guardrail #5).
|
|
"""
|
|
|
|
from app.core import clock
|
|
from app.db import identity_repo
|
|
from app.db.identity import ConnectCode
|
|
from app.db.session import get_identity_session
|
|
from app.integrations.base import VerifiedPerson
|
|
|
|
CODE_MAX_LEN = 32
|
|
|
|
|
|
class EmiasMock:
|
|
async def verify(self, auth_code: str) -> VerifiedPerson | None:
|
|
code = auth_code.strip().upper()
|
|
if not code or len(code) > CODE_MAX_LEN:
|
|
return None
|
|
async with get_identity_session() as s:
|
|
row = await s.get(ConnectCode, code)
|
|
if row is None:
|
|
return None
|
|
token = await identity_repo.redeem_connect_code(s, code=code, now=clock.now())
|
|
if token is None:
|
|
return None
|
|
return VerifiedPerson(token=token, doctor_id=row.doctor_id)
|