Skip to content

feat(client)!: рабочая авторизация в IXrplClient — Basic на ws-хендшейке и admin-креды по WS (10.10.0.0) - #67

Merged
Platonenkov merged 3 commits into
devfrom
claude/ixrplclient-authorization-002a6a
Jul 29, 2026
Merged

feat(client)!: рабочая авторизация в IXrplClient — Basic на ws-хендшейке и admin-креды по WS (10.10.0.0)#67
Platonenkov merged 3 commits into
devfrom
claude/ixrplclient-authorization-002a6a

Conversation

@Platonenkov

@Platonenkov Platonenkov commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Проблема

ConnectionOptions.authorization была публичной опцией на XrplClient.ClientOptions с момента порта с xrpl.js, но никуда не подключена. Connection.CreateWebSocket — блок закомментированного JS-псевдокода, заканчивающийся одной живой строкой:

WebSocketClient.Create(url); // todo add options

WebSocketClient.Create(string uri) параметр config вообще не принимал. Ни authorization, ни headers, ни proxy, ни сертификаты до сокета не доходили — опция выглядела рабочей и молча игнорировалась.

Что важно знать про rippled

В rippled две несвязанные схемы аутентификации на port-станзе, и для WebSocket работает только вторая:

Настройка Как передаётся Где действует
user / password HTTP Basic заголовок только HTTP/HTTPS
admin_user / admin_password поля внутри JSON запроса HTTP/S, WS, WSS

authorized() в ServerHandler.cpp вызывается ровно из одного места — onRequest(). Путь onHandoff() при websocket::is_upgrade(request) уходит в апгрейд без этой проверки. То есть Basic-заголовок на ws/wss-рукопожатии сама нода не смотрит: опция authorization осмысленна для reverse-proxy или провайдера перед нодой, а для админ-команд по WS нужен второй механизм. Поэтому в PR реализованы оба.

Изменения

1. authorization и headers доходят до сокета

  • authorizationAuthorization: Basic base64(value) на WebSocket-апгрейде, как в xrpl.js createWebSocket; значение — сырая пара user:password, base64 делает SDK
  • headers прокидываются как есть; тип сменён Dictionary<string, object>Dictionary<string, string> (как в xrpl.js). Формально source-breaking, фактически нет: поле было инертным
  • оба игнорируются под WebAssembly — браузерный WebSocket API не умеет ставить заголовки запроса, поэтому ClientWebSocket.Options.SetRequestHeader под тем же guard'ом OperatingSystem.IsBrowser(), что уже стоял на KeepAliveInterval
  • proxy, proxyAuthorization, trustedCertificates, key, passphrase, certificate и неиспользуемая пара trace/Trace удалены, а не реализованы (breaking, без grace-периода [Obsolete] — как при удалении hex-хелперов в 10.9.0.0): актуальный xrpl.js их тоже выбросил, единообразно поддержать их на всех таргетах ClientWebSocket нельзя, и ни одно из них никогда не читалось. Свойство, которое молча ничего не делает, хуже того, которое не компилируется

2. AdminUser / AdminPassword

  • передаются полями внутри JSON запроса — единственный механизм, который rippled принимает для ws/wss
  • отправляются только когда заданы оба, что зеркалит проверку в rippled (нужны одновременно совпадение IP с admin-сетями и корректные креды)
  • подмешиваются в сериализованный JSON, а не в объект запроса, — чтобы пароль не попадал в текст TimeoutException, который потребители логируют
  • новый параметр в RequestManager.CreateRequest/CreateGRequest стоит последним, поэтому существующие позиционные вызовы не сломаны

Тесты

TestUAuthorization (7 юнит-тестов) ассертит по сырому тексту HTTP-апгрейда, пойманному локальным сокет-сервером: Basic-заголовок присутствует и корректно закодирован, кастомные заголовки на месте, Authorization отсутствует когда опция не задана, admin-креды попадают в оба вида запросов и не попадают в текст таймаут-исключения.

TestIAdminCredentials работает против новой станзы [port_ws_admin_auth] (порт 6007, с admin_user/admin_password) и проверяет обе стороны. Порт отделён от port_ws_admin, поэтому остальная интеграционная сюита не затронута; станза и проброс порта добавлены в оба стенда.

Прогон на стенде уточнил одно ожидание: порт с заданными кредами отвергает запрос без них не с noPermission, а с forbidden / Bad credentials.requestRole возвращает Role::FORBID и до гостевой роли клиента не понижает. Ассерт исправлен на фактическое поведение, и он получился строже: доказывает, что нода отвергла именно креды, а не просто отказала в недоступной команде.

Проверено

  • dotnet test --filter "TestU"865/865 зелёные
  • dotnet test --filter "TestI&TestCategory!=Live" на standalone-стенде — 219 прошли, 40 пропущены (amendment-gated на CI-стенде, штатно), 0 падений. Новый порт 6007 поднимается штатно: Opened 'port_ws_admin_auth' (ip=0.0.0.0:6007, admin nets:0.0.0.0/0, ::/0, ws)
  • dotnet build XrplCSharp.sln — 0 ошибок (единственное использование удалённого proxy было в TestConnection.cs и заменено на проверку новых дефолтов; больше в решении эти свойства нигде не читались)
  • RED проверен честно: с временно отключённой установкой заголовков падают ровно два соответствующих теста, по ассерту на реальном тексте рукопожатия

Summary by CodeRabbit

  • New Features

    • WebSocket connections now support Basic authorization and custom handshake headers.
    • Admin credentials can be securely included in WebSocket request payloads for protected commands.
    • Added an authenticated admin WebSocket endpoint for standalone and CI environments.
  • Documentation

    • Updated release notes and standalone node guides with WebSocket authentication behavior and port details.
  • Tests

    • Added coverage for handshake headers, credential handling, protected admin commands, and sensitive-data protection.

…ке и admin-креды по WS

ConnectionOptions.authorization была публичной опцией с момента порта с xrpl.js,
но Connection.CreateWebSocket представлял собой блок закомментированного JS-псевдокода,
заканчивающийся `WebSocketClient.Create(url); // todo add options`, а сам WebSocketClient
не имел параметра, чтобы их принять. Ничто из authorization/headers/proxy/сертификатов
до сокета не доходило.

- authorization -> `Authorization: Basic base64(user:pass)` на WebSocket-апгрейде,
  как в xrpl.js createWebSocket
- headers прокидываются как есть; тип сменён с Dictionary<string, object> на
  Dictionary<string, string> (source-breaking, но поле было инертным)
- оба игнорируются под WebAssembly: браузерный WebSocket API не умеет ставить
  заголовки запроса, поэтому SetRequestHeader под guard'ом OperatingSystem.IsBrowser()
- proxy, proxyAuthorization, trustedCertificates, key, passphrase, certificate
  помечены [Obsolete]: актуальный xrpl.js их тоже выбросил

Отдельно добавлены AdminUser/AdminPassword — механизм, который rippled реально
принимает для ws/wss: admin_user/admin_password передаются полями внутри JSON запроса.
Basic-заголовок на ws-хендшейке нода не проверяет (authorized() вызывается только из
HTTP-пути onRequest(), onHandoff() апгрейдит WebSocket без него), поэтому authorization
осмысленна лишь для reverse-proxy перед нодой. Креды подмешиваются в сериализованный
JSON, а не в объект запроса, чтобы пароль не попадал в текст TimeoutException.

Тесты: TestUAuthorization ассертит по сырому тексту HTTP-апгрейда, пойманному
локальным сокет-сервером. TestIAdminCredentials работает против новой станзы
[port_ws_admin_auth] (порт 6007) на standalone-стенде и проверяет обе стороны —
ledger_accept с noPermission без кредов и успешный с ними; порт отделён от
port_ws_admin, чтобы не задеть остальную интеграционную сюиту.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: WebSocket Basic auth and admin credentials for IXrplClient.

Comment @coderabbitai help to get the list of available commands.

…не noPermission

Прогон на standalone-стенде показал, что порт с заданными admin_user/admin_password
отвергает запрос без кредов с `forbidden` / `Bad credentials.`: requestRole возвращает
Role::FORBID и до гостевой роли клиент не понижается, поэтому noPermission не приходит.
Ассерт стал точнее — он теперь доказывает, что нода отвергла именно креды, а не просто
отказала в недоступной команде.

Позитивный сценарий (ledger_accept с кредами) прошёл сразу — механизм передачи
admin-кредов в теле JSON работает end-to-end.

Формулировки в CHANGES.md и комментарий к [port_ws_admin_auth] приведены в соответствие.
…Options

proxy, proxyAuthorization, trustedCertificates, key, passphrase, certificate и
неиспользуемая пара trace/Trace удалены вместо пометки [Obsolete]. Ни одно из
этих полей никогда не читалось: CreateWebSocket их игнорировал, WebSocketClient
не имел параметра, чтобы их принять. Актуальный xrpl.js эти опции тоже выбросил,
а единообразно поддержать их на всех таргетах ClientWebSocket нельзя.

BREAKING CHANGE: свойства удалены без grace-периода [Obsolete] — как и при
удалении hex-хелперов в 10.9.0.0. Работающий код на них опираться не мог:
любое присвоение молча ничего не делало.

authorization и headers сохранены — они реализованы в этом же релизе.
@Platonenkov Platonenkov changed the title feat(client): рабочая авторизация в IXrplClient — Basic на ws-хендшейке и admin-креды по WS (10.10.0.0) feat(client)!: рабочая авторизация в IXrplClient — Basic на ws-хендшейке и admin-креды по WS (10.10.0.0) Jul 29, 2026
@Platonenkov
Platonenkov added this pull request to the merge queue Jul 29, 2026
Merged via the queue into dev with commit fb14dd0 Jul 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant