# QR-LOGIN — Код-подтверждение входа по QR Стандарт на «reverse»-подтверждение: **телефон сканирует QR и сам отправляет подтверждение** сервису, вместо того чтобы сервис заранее знал адрес пользователя. Работает и для входа, и для регистрации, и не требует изменений в протоколе TCE — используется обычный объект `ApprovalRequest`. Этот документ задаёт единый формат QR и правила взаимодействия: его обязаны понимать (а) клиент-кошелёк (Android), который печатает QR на камеру и (б) сервис, который QR показывает. ## Формат QR QR кодирует компактный JSON (намеренно без пробелов): ```json {"v":1,"type":"nt-login","relay":"https://trust.n1ko.dev","svc":"trust1qz...","code":"482913","msg":"Вход в Forgejo"} ``` | Поле | Обязательно | Тип | Смысл | |-------|-------------|--------------------|---------------------------------------------------------------| | `v` | да | целое = `1` | версия формата; клиент отвергает незнакомое | | `type`| да | строка = `nt-login`| тип QR; клиент игнорирует остальные | | `relay`| да | URL | база релея (http/https/wss), куда класть подтверждение | | `svc` | да | адрес `trust1...` | адрес сервиса, которому адресовано подтверждение (получатель) | | `code`| да | ровно 6 цифр | одноразовый код для сверки (см. ниже) | | `msg` | нет | строка ≤ 64 | человекочитаемое имя запроса, напр. «Вход в Forgejo» | Сервис, показывающий QR, сам генерирует `code` (случайно) и показывает его рядом на экране крупно — это код анти-фишинга, а НЕ аутентификатор. ## Поток ``` браузер/сервис телефон (клиент) релей | показывает QR | | | {relay, svc, code, msg} | | |-------------------------------->| скан + показ диалога | | | «msg? сервис short_svc» | | | код 482913 (сверка) | | пользователь жмёт Подтвердить | | | | mint ApprovalRequest{ | | | sender: я, | | | recipient: svc, | | | action: "login", | | | payload:{code}, | | | ttl ≤ 60s } | | |-- store ------------------->| | poll /v1/requests?recipient=svc| | | <- envelope {tce,sig} ---------| | | VerifyApprovalRequest | | | match payload.code + свежесть | | | user = req.sender -> сессия | | ``` Суть «reverse»-режима: подписанный `ApprovalRequest` от **пользователя** является подтверждением личности. Сервису не нужно заранее знать адрес — он получает его из проверенной подписи отправителя. ## Правила для клиента (кошелька) 1. Валидация QR: `v == 1`, `type == "nt-login"`, `relay` — валидный URL, `svc` — валидный адрес `trust1...` (каноническая проверка public key), `code` — ровно 6 цифр, `msg`, если есть, ≤ 64 символов. Любое нарушение — отказать с объяснением. 2. Диалог подтверждения до отправки: - заголовок = `msg` (если есть, иначе «Вход в сервис»); - обязательно показать **короткий адрес сервиса** рядом с `msg` — имя самозаявлено, ему не доверяют (см. INV-7, алиасы неавторитетны); - крупно показать `code` для визуальной сверки с экраном браузера. 3. Отправить один `ApprovalRequest`: - `sender` = ключ пользователя (подпись ставит клиент); - `recipient` = `svc`; - `action` = `"login"`; - `payload` = `{"code": ""}` (код попадает внутрь подписи); - `created_at` = now, `expires_at` = now + ≤60 (ограничение формата TCE); - записать в историю. 4. Не показывать `code` в истории постоянно; пометить статусом «отправлено». ## Правила для сервиса 1. Сгенерировать одноразовый `code` и показывать QR вместе с ним. 2. Опрашивать `/v1/requests?recipient=<свой адрес>&after=<курсор>` (или подписаться на канал `requests` по WS) со своей сессией чтения. 3. Для каждого конверта: `VerifyApprovalRequest` → проверить свежесть окна (в границах `created_at..expires_at` за 60 с) → сопоставить `payload.code` со своим активным QR. 4. Совпадение по `code` + прошедшая подпись ⇒ личность = `req.sender`. Выпустить собственную сессию для этого адреса. 5. (Опционально) закрыть петлю: подписать `ApprovalResponse{Allow}` на этот request и сохранить — клиент увидит «approved» в истории. ## Безопасность - **Релей не может подставить адрес.** Подпись отправляет только владелец ключа; релей видит всё, но подписать ничьим именем не может (максимум — цензура/задержка, что неотличимо от отказа в обслуживании). - **`code` — не аутентификатор**, а метка для выборки и визуальной сверки. Он зашит внутрь подписанного payload, поэтому его нельзя вырвать из подлинного запроса и подставить в поддельный. - **Анти-фишинг:** если злоумышленник показывает на сайте чужой QR, код на телефоне не совпадёт с кодом на экране браузера — заметно сразу. - **Replay/одноразовость:** `code` живёт не дольше окна запроса (≤ 60 с); сервис принимает каждый code один раз. - **`msg` самозаявлена** и показывается только рядом с верифицированным адресом; она нужна для узнавания, а не для доверия. ## Совместимые SDK Готовые реализации чтения/отправки и серверной верификации: - **Android-кошелёк**: `:sdk` (niko_trust_android) — парсер QR + `sendLogin`. - **Server/Go**: `pkg/protocol.VerifyApprovalRequest`, пример поллинга — `examples/service`. - **Rust**: клиентская библиотека `niko_trust_gui` (модули `protocol`/`relay`).