Skip to content
Snippets Groups Projects
Commit b645a9de authored by Nicolas Werner's avatar Nicolas Werner
Browse files

Add user interactive auth to error returned by request

parent 5fbee216
No related branches found
No related tags found
No related merge requests found
......@@ -163,6 +163,7 @@ target_sources(matrix_client
lib/structs/identifiers.cpp
lib/structs/pushrules.cpp
lib/structs/requests.cpp
lib/structs/user_interactive.cpp
lib/structs/events/aliases.cpp
lib/structs/events/avatar.cpp
lib/structs/events/canonical_alias.cpp
......
......@@ -26,5 +26,7 @@
#include "mtx/events/messages/text.hpp"
#include "mtx/events/messages/video.hpp"
#include "mtx/user_interactive.hpp"
#include "mtx/requests.hpp"
#include "mtx/responses.hpp"
......@@ -7,6 +7,8 @@
#endif
#include <string>
#include "user_interactive.hpp"
namespace mtx {
namespace errors {
......@@ -62,6 +64,9 @@ struct Error
ErrorCode errcode = {};
//! Human readable version of the error.
std::string error;
//! Auth flows in case of 401
user_interactive::Unauthorized unauthorized;
};
void
......
#pragma once
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
#include <nlohmann/json.hpp>
namespace mtx::user_interactive {
using AuthType = std::string;
namespace auth_types {
constexpr std::string_view password = "m.login.password";
constexpr std::string_view recaptcha = "m.login.recaptcha";
constexpr std::string_view oauth2 = "m.login.oauth2";
constexpr std::string_view email_identity = "m.login.email.identity";
constexpr std::string_view msisdn = "m.login.msisdn";
constexpr std::string_view token = "m.login.token";
constexpr std::string_view dummy = "m.login.dummy";
constexpr std::string_view terms = "m.login.terms"; // see MSC1692
}
using Stages = std::vector<AuthType>;
struct Flow
{
Stages stages;
};
void
from_json(const nlohmann::json &obj, Flow &flow);
struct OAuth2Params
{
std::string uri;
};
void
from_json(const nlohmann::json &obj, OAuth2Params &params);
struct PolicyDescription
{
std::string name; // language specific name
std::string url; // language specific link
};
void
from_json(const nlohmann::json &obj, PolicyDescription &desc);
struct Policy
{
std::string version;
// 2 letter language code to policy name and link, fallback to "en"
// recommended, when language not available.
std::unordered_map<std::string, PolicyDescription> langToPolicy;
};
void
from_json(const nlohmann::json &obj, Policy &policy);
struct TermsParams
{
std::unordered_map<std::string, Policy> policies;
};
void
from_json(const nlohmann::json &obj, TermsParams &params);
using Params = std::variant<OAuth2Params, TermsParams, nlohmann::json>;
struct Unauthorized
{
// completed stages
Stages completed;
// session key to provide to further auth stages
std::string session;
// list of flows, which can be used to complete the UI auth
std::vector<Flow> flows;
// AuthType may be an undocumented string, not defined in auth_types
std::unordered_map<AuthType, Params> params;
};
void
from_json(const nlohmann::json &obj, Unauthorized &unauthorized);
}
......@@ -2,8 +2,7 @@
#include <nlohmann/json.hpp>
namespace mtx {
namespace errors {
namespace mtx::errors {
std::string
to_string(ErrorCode code)
{
......@@ -86,7 +85,9 @@ void
from_json(const nlohmann::json &obj, Error &error)
{
error.errcode = from_string(obj.at("errcode").get<std::string>());
error.error = obj.at("error").get<std::string>();
}
error.error = obj.value("error", "");
if (obj.contains("session"))
error.unauthorized = obj.get<user_interactive::Unauthorized>();
}
}
#include "mtx/user_interactive.hpp"
namespace mtx::user_interactive {
void
from_json(const nlohmann::json &obj, OAuth2Params &params)
{
params.uri = obj.value("uri", "");
}
void
from_json(const nlohmann::json &obj, PolicyDescription &d)
{
d.name = obj.value("name", "");
d.url = obj.value("url", "");
}
void
from_json(const nlohmann::json &obj, Policy &policy)
{
policy.version = obj.at("version");
for (const auto &e : obj.items())
if (e.key() != "version")
policy.langToPolicy.emplace(e.key(), e.value().get<PolicyDescription>());
}
void
from_json(const nlohmann::json &obj, TermsParams &terms)
{
terms.policies = obj["policies"].get<std::unordered_map<std::string, Policy>>();
}
void
from_json(const nlohmann::json &obj, Flow &flow)
{
flow.stages = obj["stages"].get<Stages>();
}
void
from_json(const nlohmann::json &obj, Unauthorized &u)
{
if (obj.contains("completed"))
u.completed = obj.at("completed").get<Stages>();
u.session = obj.at("session");
u.flows = obj.at("flows").get<std::vector<Flow>>();
if (obj.contains("params")) {
for (const auto &e : obj["params"].items()) {
if (e.key() == auth_types::terms)
u.params.emplace(e.key(), e.value().get<TermsParams>());
else if (e.key() == auth_types::oauth2)
u.params.emplace(e.key(), e.value().get<OAuth2Params>());
else
u.params.emplace(e.key(), e.value());
}
}
}
}
......@@ -959,3 +959,88 @@ TEST(Responses, Notifications)
EXPECT_EQ(event.content.body, "I am a fish");
EXPECT_EQ(event.sender, "@alice:example.com");
}
TEST(Responses, Userinteractive)
{
json data =
R"(
{
"completed": [ "example.type.foo" ],
"session": "YQVPFRiztSYtmsjLNQmsxTCg",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAABGdGmruw 6DdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https://matrix-client.matrix.org/_matrix/consent?v=1.0"
}
}
}
}
}
})"_json;
mtx::user_interactive::Unauthorized unauthorized = data;
EXPECT_EQ(unauthorized.completed[0], "example.type.foo");
EXPECT_EQ(unauthorized.session, "YQVPFRiztSYtmsjLNQmsxTCg");
EXPECT_EQ(unauthorized.flows.size(), 2);
EXPECT_EQ(unauthorized.flows[0].stages[0], "m.login.recaptcha");
EXPECT_EQ(unauthorized.flows[0].stages[1], "m.login.terms");
EXPECT_EQ(unauthorized.flows[0].stages[2], "m.login.dummy");
EXPECT_EQ(unauthorized.flows[1].stages[0], "m.login.recaptcha");
EXPECT_EQ(unauthorized.flows[1].stages[1], "m.login.terms");
EXPECT_EQ(unauthorized.flows[1].stages[2], "m.login.email.identity");
EXPECT_EQ(std::get<mtx::user_interactive::TermsParams>(
unauthorized.params[std::string{mtx::user_interactive::auth_types::terms}])
.policies.size(),
1);
EXPECT_EQ(std::get<mtx::user_interactive::TermsParams>(
unauthorized.params[std::string{mtx::user_interactive::auth_types::terms}])
.policies["privacy_policy"]
.version,
"1.0");
EXPECT_EQ(std::get<mtx::user_interactive::TermsParams>(
unauthorized.params[std::string{mtx::user_interactive::auth_types::terms}])
.policies["privacy_policy"]
.langToPolicy["en"]
.name,
"Terms and Conditions");
json data2 = R"(
{
"session": "CFNYzCbLYyGTpURjdmkIXMHc",
"flows": [
{
"stages": [
"m.login.password"
]
}
],
"params": {}
})"_json;
unauthorized = data2;
EXPECT_EQ(unauthorized.flows[0].stages[0], mtx::user_interactive::auth_types::password);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment