From 7fe7a70fcf7540beb6d7b4847e53a425de66c6bf Mon Sep 17 00:00:00 2001 From: Nicolas Werner <nicolas.werner@hotmail.de> Date: Wed, 3 Nov 2021 18:34:59 +0100 Subject: [PATCH] Implement and fix 3pid UIA stages --- include/mtx/requests.hpp | 68 +++++++++++++++++++++++++++++++ include/mtx/responses/common.hpp | 28 +++++++++++++ include/mtx/user_interactive.hpp | 10 +++-- include/mtxclient/http/client.hpp | 33 ++++++++++++--- lib/http/client.cpp | 50 ++++++++++++++++++++++- lib/structs/requests.cpp | 25 ++++++++++++ lib/structs/responses/common.cpp | 15 +++++++ lib/structs/user_interactive.cpp | 8 ++-- tests/requests.cpp | 64 ++++++++++++----------------- 9 files changed, 248 insertions(+), 53 deletions(-) diff --git a/include/mtx/requests.hpp b/include/mtx/requests.hpp index c091a3a6f..c3b5127ef 100644 --- a/include/mtx/requests.hpp +++ b/include/mtx/requests.hpp @@ -111,6 +111,74 @@ struct Login void to_json(json &obj, const Login &request); +/// @brief Request payload for +/// `POST /_matrix/client/r0/{register,account/password}/email/requestToken` +/// +/// The homeserver should validate the email itself, either by sending a validation email itself or +/// by using a service it has control over. +struct RequestEmailToken +{ + //! Required. A unique string generated by the client, and used to identify the validation + //! attempt. It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must + //! not exceed 255 characters and it must not be empty. + std::string client_secret; + //! Required. The email address to validate. + std::string email; + + //! Required. The server will only send an email if the send_attempt is a number greater than + //! the most recent one which it has seen, scoped to that email + client_secret pair. This is to + //! avoid repeatedly sending the same email in the case of request retries between the POSTing + //! user and the identity server. The client should increment this value if they desire a new + //! email (e.g. a reminder) to be sent. If they do not, the server should respond with success + //! but not resend the email. + int send_attempt = 0; +}; + +void +to_json(json &obj, const RequestEmailToken &request); + +/// @brief Request payload for +/// `POST /_matrix/client/r0/{register,account/password}/msisdn/requestToken` +/// +/// The homeserver should validate the email itself, either by sending a validation email itself or +/// by using a service it has control over. +struct RequestMSISDNToken +{ + //! Required. A unique string generated by the client, and used to identify the validation + //! attempt. It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must + //! not exceed 255 characters and it must not be empty. + std::string client_secret; + //! Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in + //! phone_number should be parsed as if it were dialled from. + std::string country; + //! Required. The phone number to validate. + std::string phone_number; + + //! Required. The server will only send an SMS if the send_attempt is a number greater than the + //! most recent one which it has seen, scoped to that country + phone_number + client_secret + //! triple. This is to avoid repeatedly sending the same SMS in the case of request retries + //! between the POSTing user and the identity server. The client should increment this value if + //! they desire a new SMS (e.g. a reminder) to be sent. + int send_attempt = 0; +}; + +void +to_json(json &obj, const RequestMSISDNToken &request); + +//! Validate ownership of an email address/phone number. +struct IdentitySubmitToken +{ + //! Required. The session ID, generated by the requestToken call. + std::string sid; + //! Required. The client secret that was supplied to the requestToken call. + std::string client_secret; + //! Required. The token generated by the requestToken call and emailed to the user. + std::string token; +}; + +void +to_json(json &obj, const IdentitySubmitToken &request); + //! Request payload for the `PUT /_matrix/client/r0/sendToDevice/{eventType}/{transcationID}` template<typename EventContent> using ToDeviceMessages = std::map<mtx::identifiers::User, std::map<std::string, EventContent>>; diff --git a/include/mtx/responses/common.hpp b/include/mtx/responses/common.hpp index fdf7b3951..cdbc4d763 100644 --- a/include/mtx/responses/common.hpp +++ b/include/mtx/responses/common.hpp @@ -66,6 +66,34 @@ struct Version void from_json(const nlohmann::json &obj, Version &response); + +//! Some endpoints return this to indicate success in addition to the http code. +struct Success +{ + //! Required. Whether the validation was successful or not. + bool success; +}; + +void +from_json(const nlohmann::json &obj, Success &response); + +//! Responses to the `/requestToken` endpoints +struct RequestToken +{ + //! Required. The session ID. Session IDs are opaque strings that must consist entirely of the + //! characters [0-9a-zA-Z.=_-]. Their length must not exceed 255 characters and they must not be + //! empty. + std::string sid; + //! An optional field containing a URL where the client must submit the validation token to, + //! with identical parameters to the Identity Service API's POST /validate/email/submitToken + //! endpoint (without the requirement for an access token). The homeserver must send this token + //! to the user (if applicable), who should then be prompted to provide it to the client. + std::string submit_url; +}; + +void +from_json(const nlohmann::json &obj, RequestToken &response); + //! Different helper for parsing responses. namespace utils { //! Multiple account_data events. diff --git a/include/mtx/user_interactive.hpp b/include/mtx/user_interactive.hpp index b60647045..28c17eecb 100644 --- a/include/mtx/user_interactive.hpp +++ b/include/mtx/user_interactive.hpp @@ -172,15 +172,17 @@ struct ThreePIDCred //! Email authentication stage. struct EmailIdentity { - // The 3rd party ids - std::vector<ThreePIDCred> threepidCreds; + //! The 3rd party id + //! See https://github.com/matrix-org/matrix-doc/pull/3471 for context. + ThreePIDCred threepidCred; }; //! SMS authentication stage. struct MSISDN { - // The 3rd party ids - std::vector<ThreePIDCred> threepidCreds; + //! The 3rd party id + //! See https://github.com/matrix-org/matrix-doc/pull/3471 for context. + ThreePIDCred threepidCred; }; //! Registration token authentication stage. diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp index c75096424..35ba83b19 100644 --- a/include/mtxclient/http/client.hpp +++ b/include/mtxclient/http/client.hpp @@ -55,10 +55,9 @@ struct AvatarUrl; struct ClaimKeys; struct ContentURI; struct CreateRoom; +struct Device; struct EventId; -struct RoomId; struct FilterId; -struct Version; struct GroupId; struct GroupProfile; struct JoinedGroups; @@ -69,18 +68,21 @@ struct LoginFlows; struct Messages; struct Notifications; struct Profile; +struct PublicRoomVisibility; +struct PublicRooms; +struct QueryDevices; struct QueryKeys; struct Register; struct RegistrationTokenValidity; +struct RequestToken; +struct RoomId; +struct Success; struct Sync; struct TurnServer; struct UploadKeys; +struct Version; struct Versions; struct WellKnown; -struct PublicRoomVisibility; -struct PublicRooms; -struct QueryDevices; -struct Device; namespace backup { struct SessionBackup; struct RoomKeysBackup; @@ -279,6 +281,25 @@ public: void registration_token_validity(const std::string token, Callback<mtx::responses::RegistrationTokenValidity> cb); + //! Validate an unused email address. + void register_email_request_token(const requests::RequestEmailToken &r, + Callback<mtx::responses::RequestToken> cb); + //! Validate a used email address. + void verify_email_request_token(const requests::RequestEmailToken &r, + Callback<mtx::responses::RequestToken> cb); + + //! Validate an unused phone number. + void register_phone_request_token(const requests::RequestMSISDNToken &r, + Callback<mtx::responses::RequestToken> cb); + //! Validate a used phone number. + void verify_phone_request_token(const requests::RequestMSISDNToken &r, + Callback<mtx::responses::RequestToken> cb); + + //! Validate ownership of an email address/phone number. + void validate_submit_token(const std::string &url, + const requests::IdentitySubmitToken &r, + Callback<mtx::responses::Success>); + //! Paginate through the list of events that the user has been, //! or would have been notified about. void notifications(uint64_t limit, diff --git a/lib/http/client.cpp b/lib/http/client.cpp index 34141f6cf..38fc62fa4 100644 --- a/lib/http/client.cpp +++ b/lib/http/client.cpp @@ -2,6 +2,7 @@ #include "mtx/log.hpp" #include "mtxclient/http/client_impl.hpp" +#include <iostream> #include <mutex> #include <thread> @@ -917,9 +918,10 @@ Client::registration(const std::string &user, "/client/r0/register", request, [cb, h](auto &r, RequestErr e) { - if (e && e->status_code == 401) + if (e && e->status_code == 401) { + std::cout << e->matrix_error.error << "\n"; h.prompt(h, e->matrix_error.unauthorized); - else + } else cb(r, e); }, false); @@ -943,6 +945,50 @@ Client::registration_token_validity(const std::string token, }); } +void +Client::register_email_request_token(const requests::RequestEmailToken &r, + Callback<mtx::responses::RequestToken> cb) +{ + post("/client/r0/register/email/requestToken", r, cb); +} +void +Client::verify_email_request_token(const requests::RequestEmailToken &r, + Callback<mtx::responses::RequestToken> cb) +{ + post("/client/r0/account/password/email/requestToken", r, cb); +} + +void +Client::register_phone_request_token(const requests::RequestMSISDNToken &r, + Callback<mtx::responses::RequestToken> cb) +{ + post("/client/r0/register/msisdn/requestToken", r, cb); +} +void +Client::verify_phone_request_token(const requests::RequestMSISDNToken &r, + Callback<mtx::responses::RequestToken> cb) +{ + post("/client/r0/account/password/msisdn/requestToken", r, cb); +} + +void +Client::validate_submit_token(const std::string &url, + const requests::IdentitySubmitToken &r, + Callback<mtx::responses::Success> cb) +{ + // some dancing to send to an arbitrary, server provided url + auto callback = prepare_callback<mtx::responses::Success>( + [cb](const mtx::responses::Success &res, HeaderFields, RequestErr err) { cb(res, err); }); + p->client.post( + url, + json(r).dump(), + "application/json", + [callback](const coeurl::Request &r) { + callback(r.response_headers(), r.response(), r.error_code(), r.response_code()); + }, + prepare_headers(false)); +} + void Client::send_state_event(const std::string &room_id, const std::string &event_type, diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp index a55dfccdd..de4ab9704 100644 --- a/lib/structs/requests.cpp +++ b/lib/structs/requests.cpp @@ -84,6 +84,31 @@ to_json(json &obj, const Login &request) obj["type"] = request.type; } +void +to_json(json &obj, const RequestEmailToken &request) +{ + obj["client_secret"] = request.client_secret; + obj["email"] = request.email; + obj["send_attempt"] = request.send_attempt; +} + +void +to_json(json &obj, const RequestMSISDNToken &request) +{ + obj["client_secret"] = request.client_secret; + obj["country"] = request.country; + obj["phone_number"] = request.phone_number; + obj["send_attempt"] = request.send_attempt; +} + +void +to_json(json &obj, const IdentitySubmitToken &request) +{ + obj["sid"] = request.sid; + obj["client_secret"] = request.client_secret; + obj["token"] = request.token; +} + void to_json(json &obj, const AvatarUrl &request) { diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp index 366815f7d..158854f29 100644 --- a/lib/structs/responses/common.cpp +++ b/lib/structs/responses/common.cpp @@ -59,6 +59,21 @@ from_json(const nlohmann::json &obj, Version &response) response.version = obj.at("version"); } +void +from_json(const nlohmann::json &obj, Success &success) +{ + success.success = obj.at("success"); +} + +void +from_json(const nlohmann::json &obj, RequestToken &r) +{ + r.sid = obj.at("sid"); + + if (obj.contains("submit_url")) + r.submit_url = obj.at("submit_url"); +} + namespace utils { void diff --git a/lib/structs/user_interactive.cpp b/lib/structs/user_interactive.cpp index 61b2ffa27..e79914420 100644 --- a/lib/structs/user_interactive.cpp +++ b/lib/structs/user_interactive.cpp @@ -110,12 +110,12 @@ to_json(nlohmann::json &obj, const Auth &auth) obj["txn_id"] = token.txn_id; }, [&obj](const auth::EmailIdentity &id) { - obj["type"] = auth_types::email_identity; - obj["threepidCreds"] = id.threepidCreds; + obj["type"] = auth_types::email_identity; + obj["threepid_creds"] = id.threepidCred; }, [&obj](const auth::MSISDN &id) { - obj["type"] = auth_types::msisdn; - obj["threepidCreds"] = id.threepidCreds; + obj["type"] = auth_types::msisdn; + obj["threepid_creds"] = id.threepidCred; }, [&obj](const auth::RegistrationToken ®istration_token) { obj["type"] = auth_types::registration_token; diff --git a/tests/requests.cpp b/tests/requests.cpp index 072de255b..a348ee055 100644 --- a/tests/requests.cpp +++ b/tests/requests.cpp @@ -225,58 +225,48 @@ TEST(Requests, UserInteractiveAuth) "session": "<session ID>" })"_json); - a.content = auth::EmailIdentity{{ - {"<identity server session id>", - "<identity server client secret>", - "<url of identity server authed with, e.g. 'matrix.org:8090'>", - "<access token previously registered with the identity server>"}, - }}; + a.content = + auth::EmailIdentity{"<identity server session id>", + "<identity server client secret>", + "<url of identity server authed with, e.g. 'matrix.org:8090'>", + "<access token previously registered with the identity server>"}; EXPECT_EQ(nlohmann::json(a), R"({ "type": "m.login.email.identity", - "threepidCreds": [ - { - "sid": "<identity server session id>", - "client_secret": "<identity server client secret>", - "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>", - "id_access_token": "<access token previously registered with the identity server>" - } - ], + "threepid_creds": { + "sid": "<identity server session id>", + "client_secret": "<identity server client secret>", + "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>", + "id_access_token": "<access token previously registered with the identity server>" + }, "session": "<session ID>" })"_json); - a.content = auth::MSISDN{{ - {"<identity server session id>", - "<identity server client secret>", - "<url of identity server authed with, e.g. 'matrix.org:8090'>", - "<access token previously registered with the identity server>"}, - }}; + a.content = auth::MSISDN{"<identity server session id>", + "<identity server client secret>", + "<url of identity server authed with, e.g. 'matrix.org:8090'>", + "<access token previously registered with the identity server>"}; EXPECT_EQ(nlohmann::json(a), R"({ "type": "m.login.msisdn", - "threepidCreds": [ - { - "sid": "<identity server session id>", - "client_secret": "<identity server client secret>", - "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>", - "id_access_token": "<access token previously registered with the identity server>" - } - ], + "threepid_creds": { + "sid": "<identity server session id>", + "client_secret": "<identity server client secret>", + "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>", + "id_access_token": "<access token previously registered with the identity server>" + }, "session": "<session ID>" })"_json); - a.content = auth::MSISDN{{ - {"<identity server session id>", "<identity server client secret>", "", ""}, - }}; + a.content = + auth::MSISDN{"<identity server session id>", "<identity server client secret>", "", ""}; EXPECT_EQ(nlohmann::json(a), R"({ "type": "m.login.msisdn", - "threepidCreds": [ - { - "sid": "<identity server session id>", - "client_secret": "<identity server client secret>" - } - ], + "threepid_creds": { + "sid": "<identity server session id>", + "client_secret": "<identity server client secret>" + }, "session": "<session ID>" })"_json); -- GitLab