feat(web): patient web app with ЕМИАС (demo) and anonymous sign-in
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>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""ЕМИАС 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)
|
||||
Reference in New Issue
Block a user