Ring Daemon 16.0.0
Loading...
Searching...
No Matches
server_account_manager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
18#include "base64.h"
19#include "jami/account_const.h"
20#include "fileutils.h"
21
22#include <opendht/http.h>
23#include <opendht/log.h>
24#include <opendht/thread_pool.h>
25
26#include "conversation_module.h"
27#include "jamiaccount.h"
28#include "manager.h"
29
30#include <algorithm>
31#include <string_view>
32
33using namespace std::literals;
34
35namespace jami {
36
37using Request = dht::http::Request;
38
39#define JAMI_PATH_LOGIN "/api/login"
40#define JAMI_PATH_AUTH "/api/auth"
41constexpr std::string_view PATH_DEVICE = JAMI_PATH_AUTH "/device";
42constexpr std::string_view PATH_DEVICES = JAMI_PATH_AUTH "/devices";
43constexpr std::string_view PATH_SEARCH = JAMI_PATH_AUTH "/directory/search";
44constexpr std::string_view PATH_CONTACTS = JAMI_PATH_AUTH "/contacts";
45constexpr std::string_view PATH_CONVERSATIONS = JAMI_PATH_AUTH "/conversations";
46constexpr std::string_view PATH_CONVERSATIONS_REQUESTS = JAMI_PATH_AUTH "/conversationRequests";
47constexpr std::string_view PATH_BLUEPRINT = JAMI_PATH_AUTH "/policyData";
48
49ServerAccountManager::ServerAccountManager(const std::string& accountId,
50 const std::filesystem::path& path,
51 const std::string& managerHostname,
52 const std::string& nameServer)
53 : AccountManager(accountId, path, nameServer)
54 , managerHostname_(managerHostname)
55 , logger_(Logger::dhtLogger()) {}
56
57void
58ServerAccountManager::setAuthHeaderFields(Request& request) const
59{
60 request.set_header_field(restinio::http_field_t::authorization, "Bearer " + token_);
61}
62
63void
65 std::string deviceName,
66 std::unique_ptr<AccountCredentials> credentials,
67 AuthSuccessCallback onSuccess,
68 AuthFailureCallback onFailure,
70{
71 auto ctx = std::make_shared<AuthContext>();
72 ctx->accountId = accountId_;
73 ctx->key = key;
74 ctx->request = buildRequest(key);
75 ctx->deviceName = std::move(deviceName);
76 ctx->credentials = dynamic_unique_cast<ServerAccountCredentials>(std::move(credentials));
77 ctx->onSuccess = std::move(onSuccess);
78 ctx->onFailure = std::move(onFailure);
79 if (not ctx->credentials or ctx->credentials->username.empty()) {
80 ctx->onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
81 return;
82 }
83
84 onChange_ = std::move(onChange);
85 const std::string url = managerHostname_ + PATH_DEVICE;
86 JAMI_WARNING("[Account {}] [Auth] Authentication with: {} to {}", accountId_, ctx->credentials->username, url);
87
88 dht::ThreadPool::computation().run([ctx, url, w=weak_from_this()] {
89 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
90 if (not this_) return;
91 Json::Value body;
92 {
93 auto csr = ctx->request.get()->toString();
94 body["csr"] = csr;
95 body["deviceName"] = ctx->deviceName;
96 }
97 auto request = std::make_shared<Request>(
98 *Manager::instance().ioContext(),
99 url,
100 body,
101 [ctx, w](Json::Value json, const dht::http::Response& response) {
102 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
103 JAMI_DEBUG("[Auth] Got request callback with status code={}",
104 response.status_code);
105 if (response.status_code == 0 || this_ == nullptr)
106 ctx->onFailure(AuthError::SERVER_ERROR, "Unable to connect to server");
107 else if (response.status_code >= 400 && response.status_code < 500)
108 ctx->onFailure(AuthError::INVALID_ARGUMENTS, "Invalid credentials provided!");
109 else if (response.status_code < 200 || response.status_code > 299)
110 ctx->onFailure(AuthError::INVALID_ARGUMENTS, "");
111 else {
112 do {
113 try {
114 JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
115 auto cert = std::make_shared<dht::crypto::Certificate>(
116 json["certificateChain"].asString());
117 auto accountCert = cert->issuer;
118 if (not accountCert) {
119 JAMI_ERR("[Auth] Unable to parse certificate: no issuer");
120 ctx->onFailure(AuthError::SERVER_ERROR,
121 "Invalid certificate from server");
122 break;
123 }
124 auto receipt = json["deviceReceipt"].asString();
125 Json::Value receiptJson;
126 std::string err;
127 auto receiptReader = std::unique_ptr<Json::CharReader>(
128 Json::CharReaderBuilder {}.newCharReader());
129 if (!receiptReader->parse(receipt.data(),
130 receipt.data() + receipt.size(),
132 &err)) {
133 JAMI_ERR("[Auth] Unable to parse receipt from server: %s",
134 err.c_str());
135 ctx->onFailure(AuthError::SERVER_ERROR,
136 "Unable to parse receipt from server");
137 break;
138 }
139 auto receiptSignature = base64::decode(
140 json["receiptSignature"].asString());
141
142 auto info = std::make_unique<AccountInfo>();
143 info->identity.first = ctx->key.get();
144 info->identity.second = cert;
145 info->devicePk = cert->getSharedPublicKey();
146 info->deviceId = info->devicePk->getLongId().toString();
147 info->accountId = accountCert->getId().toString();
148 info->contacts = std::make_unique<ContactList>(ctx->accountId,
150 this_->path_,
151 this_->onChange_);
152 // info->contacts->setContacts(a.contacts);
153 if (ctx->deviceName.empty())
154 ctx->deviceName = info->deviceId.substr(8);
155 info->contacts->foundAccountDevice(cert,
156 ctx->deviceName,
157 clock::now());
158 info->ethAccount = receiptJson["eth"].asString();
159 info->announce
160 = parseAnnounce(receiptJson["announce"].asString(),
161 info->accountId,
162 info->devicePk->getId().toString(),
163 info->devicePk->getLongId().toString());
164 if (not info->announce) {
165 ctx->onFailure(AuthError::SERVER_ERROR,
166 "Unable to parse announce from server");
167 return;
168 }
169 info->username = ctx->credentials->username;
170
171 this_->creds_ = std::move(ctx->credentials);
172 this_->info_ = std::move(info);
173 std::map<std::string, std::string> config;
174 for (auto itr = json.begin(); itr != json.end(); ++itr) {
175 const auto& name = itr.name();
176 if (name == "nameServer"sv) {
177 auto nameServer = json["nameServer"].asString();
178 if (!nameServer.empty() && nameServer[0] == '/')
179 nameServer = this_->managerHostname_ + nameServer;
180 this_->nameDir_ = NameDirectory::instance(nameServer);
181 config
183 std::move(nameServer));
184 } else if (name == "userPhoto"sv) {
185 this_->info_->photo = json["userPhoto"].asString();
186 } else {
187 config.emplace(name, itr->asString());
188 }
189 }
190
191 ctx->onSuccess(*this_->info_,
192 std::move(config),
193 std::move(receipt),
194 std::move(receiptSignature));
195 } catch (const std::exception& e) {
196 JAMI_ERROR("[Account {}] [Auth] Error when loading account: {}", this_->accountId_, e.what());
197 ctx->onFailure(AuthError::NETWORK, "");
198 }
199 } while (false);
200 }
201
202 if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
203 this_->clearRequest(response.request);
204 },
205 this_->logger_);
206 request->set_auth(ctx->credentials->username, ctx->credentials->password);
207 this_->sendRequest(request);
208 });
209}
210
211void
212ServerAccountManager::onAuthEnded(const Json::Value& json,
213 const dht::http::Response& response,
214 TokenScope expectedScope)
215{
216 if (response.status_code >= 200 && response.status_code < 300) {
217 auto scopeStr = json["scope"].asString();
218 auto scope = scopeStr == "DEVICE"sv
219 ? TokenScope::Device
220 : (scopeStr == "USER"sv ? TokenScope::User : TokenScope::None);
221 auto expires_in = json["expires_in"].asLargestUInt();
222 auto expiration = std::chrono::steady_clock::now() + std::chrono::seconds(expires_in);
223 JAMI_WARNING("[Account {}] [Auth] Got server response: {} ({} bytes)", accountId_, response.status_code, response.body.size());
224 setToken(json["access_token"].asString(), scope, expiration);
225 } else {
226 JAMI_WARNING("[Account {}] [Auth] Got server response: {} ({} bytes)", accountId_, response.status_code, response.body.size());
227 authFailed(expectedScope, response.status_code);
228 }
229 clearRequest(response.request);
230}
231
232void
233ServerAccountManager::authenticateDevice()
234{
235 if (not info_) {
236 authFailed(TokenScope::Device, 0);
237 }
238 const std::string url = managerHostname_ + JAMI_PATH_LOGIN;
239 JAMI_WARNING("[Account {}] [Auth] Getting a device token: {}", accountId_, url);
240 auto request = std::make_shared<Request>(
241 *Manager::instance().ioContext(),
242 url,
243 Json::Value {Json::objectValue},
244 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
245 if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
246 this_->onAuthEnded(json, response, TokenScope::Device);
247 },
248 logger_);
249 request->set_identity(info_->identity);
250 // request->set_certificate_authority(info_->identity.second->issuer->issuer);
251 sendRequest(request);
252}
253
254void
255ServerAccountManager::authenticateAccount(const std::string& username, const std::string& password)
256{
257 const std::string url = managerHostname_ + JAMI_PATH_LOGIN;
258 JAMI_WARNING("[Account {}] [Auth] Getting a user token: {}", accountId_, url);
259 auto request = std::make_shared<Request>(
260 *Manager::instance().ioContext(),
261 url,
262 Json::Value {Json::objectValue},
263 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
264 if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()))
265 this_->onAuthEnded(json, response, TokenScope::User);
266 },
267 logger_);
268 request->set_auth(username, password);
269 sendRequest(request);
270}
271
272void
273ServerAccountManager::sendRequest(const std::shared_ptr<dht::http::Request>& request)
274{
275 request->set_header_field(restinio::http_field_t::user_agent, userAgent());
276 {
277 std::lock_guard lock(requestLock_);
278 requests_.emplace(request);
279 }
280 request->send();
281}
282
283void
284ServerAccountManager::clearRequest(const std::weak_ptr<dht::http::Request>& request)
285{
286 Manager::instance().ioContext()->post([w=weak_from_this(), request] {
287 if (auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock())) {
288 if (auto req = request.lock()) {
289 std::lock_guard lock(this_->requestLock_);
290 this_->requests_.erase(req);
291 }
292 }
293 });
294}
295
296void
297ServerAccountManager::authFailed(TokenScope scope, int code)
298{
299 RequestQueue requests;
300 {
301 std::lock_guard lock(tokenLock_);
302 requests = std::move(getRequestQueue(scope));
303 }
304 JAMI_DEBUG("[Auth] Failed auth with scope {}, ending {} pending requests",
305 (int) scope,
306 requests.size());
307 if (code == 401) {
308 // NOTE: we do not login every time to the server but retrieve a device token to use the account
309 // If authentificate device fails with 401
310 // it means that the device is revoked
311 if (onNeedsMigration_)
312 onNeedsMigration_();
313 }
314 while (not requests.empty()) {
315 auto req = std::move(requests.front());
316 requests.pop();
317 req->terminate(code == 0 ? asio::error::not_connected : asio::error::access_denied);
318 }
319}
320
321void
322ServerAccountManager::authError(TokenScope scope)
323{
324 {
325 std::lock_guard lock(tokenLock_);
326 if (scope <= tokenScope_) {
327 token_ = {};
328 tokenScope_ = TokenScope::None;
329 }
330 }
331 if (scope == TokenScope::Device)
332 authenticateDevice();
333}
334
335void
336ServerAccountManager::setToken(std::string token,
337 TokenScope scope,
338 std::chrono::steady_clock::time_point expiration)
339{
340 std::lock_guard lock(tokenLock_);
341 token_ = std::move(token);
342 tokenScope_ = scope;
343 tokenExpire_ = expiration;
344
345 nameDir_.get().setToken(token_);
346 if (not token_.empty() and scope != TokenScope::None) {
347 auto& reqQueue = getRequestQueue(scope);
348 JAMI_DEBUG("[Account {}] [Auth] Got token with scope {}, handling {} pending requests",
349 accountId_,
350 (int) scope,
351 reqQueue.size());
352 while (not reqQueue.empty()) {
353 auto req = std::move(reqQueue.front());
354 reqQueue.pop();
355 setAuthHeaderFields(*req);
356 sendRequest(req);
357 }
358 }
359}
360
361void
362ServerAccountManager::sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req)
363{
364 std::lock_guard lock(tokenLock_);
365 if (hasAuthorization(TokenScope::Device)) {
366 setAuthHeaderFields(*req);
367 sendRequest(req);
368 } else {
369 auto& rQueue = getRequestQueue(TokenScope::Device);
370 if (rQueue.empty())
371 authenticateDevice();
372 rQueue.emplace(req);
373 }
374}
375
376void
377ServerAccountManager::sendAccountRequest(const std::shared_ptr<dht::http::Request>& req,
378 const std::string& pwd)
379{
380 std::lock_guard lock(tokenLock_);
381 if (hasAuthorization(TokenScope::User)) {
382 setAuthHeaderFields(*req);
383 sendRequest(req);
384 } else {
385 auto& rQueue = getRequestQueue(TokenScope::User);
386 if (rQueue.empty())
387 authenticateAccount(info_->username, pwd);
388 rQueue.emplace(req);
389 }
390}
391
392void
393ServerAccountManager::syncDevices()
394{
395 const std::string urlDevices = managerHostname_ + PATH_DEVICES;
396 const std::string urlContacts = managerHostname_ + PATH_CONTACTS;
397 const std::string urlConversations = managerHostname_ + PATH_CONVERSATIONS;
398 const std::string urlConversationsRequests = managerHostname_ + PATH_CONVERSATIONS_REQUESTS;
399
400 JAMI_WARNING("[Account {}] [Auth] Sync conversations {}", accountId_, urlConversations);
401 Json::Value jsonConversations(Json::arrayValue);
402 for (const auto& [key, convInfo] : ConversationModule::convInfos(accountId_)) {
403 jsonConversations.append(convInfo.toJson());
404 }
405 sendDeviceRequest(std::make_shared<Request>(
406 *Manager::instance().ioContext(),
407 urlConversations,
408 jsonConversations,
409 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
410 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
411 if (!this_) return;
412 JAMI_DEBUG("[Account {}] [Auth] Got conversation sync request callback with status code={}",
413 this_->accountId_,
414 response.status_code);
415 if (response.status_code >= 200 && response.status_code < 300) {
416 try {
417 JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
418 if (not json.isArray()) {
419 JAMI_ERROR("[Account {}] [Auth] Unable to parse server response: not an array", this_->accountId_);
420 } else {
421 SyncMsg convs;
422 for (unsigned i = 0, n = json.size(); i < n; i++) {
423 const auto& e = json[i];
424 auto ci = ConvInfo(e);
425 convs.c[ci.id] = std::move(ci);
426 }
427 dht::ThreadPool::io().run([accountId=this_->accountId_, convs] {
428 if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
429 acc->convModule()->onSyncData(convs, "", "");
430 }
431 });
432 }
433 } catch (const std::exception& e) {
434 JAMI_ERROR("[Account {}] Error when iterating conversation list: {}", this_->accountId_, e.what());
435 }
436 } else if (response.status_code == 401)
437 this_->authError(TokenScope::Device);
438
439 this_->clearRequest(response.request);
440 },
441 logger_));
442
443 JAMI_WARNING("[Account {}] [Auth] Sync conversations requests {}", accountId_, urlConversationsRequests);
444 Json::Value jsonConversationsRequests(Json::arrayValue);
445 for (const auto& [key, convRequest] : ConversationModule::convRequests(accountId_)) {
446 auto jsonConversation = convRequest.toJson();
447 jsonConversationsRequests.append(std::move(jsonConversation));
448 }
449 sendDeviceRequest(std::make_shared<Request>(
450 *Manager::instance().ioContext(),
451 urlConversationsRequests,
452 jsonConversationsRequests,
453 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
454 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
455 if (!this_) return;
456 JAMI_DEBUG("[Account {}] [Auth] Got conversations requests sync request callback with status code={}",
457 this_->accountId_, response.status_code);
458 if (response.status_code >= 200 && response.status_code < 300) {
459 try {
460 JAMI_WARNING("[Account {}] [Auth] Got server response: {}", this_->accountId_, response.body);
461 if (not json.isArray()) {
462 JAMI_ERROR("[Account {}] [Auth] Unable to parse server response: not an array", this_->accountId_);
463 } else {
464 SyncMsg convReqs;
465 for (unsigned i = 0, n = json.size(); i < n; i++) {
466 const auto& e = json[i];
467 auto cr = ConversationRequest(e);
468 convReqs.cr[cr.conversationId] = std::move(cr);
469 }
470 dht::ThreadPool::io().run([accountId=this_->accountId_, convReqs] {
471 if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
472 acc->convModule()->onSyncData(convReqs, "", "");
473 }
474 });
475 }
476 } catch (const std::exception& e) {
477 JAMI_ERROR("[Account {}] Error when iterating conversations requests list: {}", this_->accountId_, e.what());
478 }
479 } else if (response.status_code == 401)
480 this_->authError(TokenScope::Device);
481
482 this_->clearRequest(response.request);
483 },
484 logger_));
485
486 JAMI_WARNING("[Account {}] [Auth] syncContacts {}", accountId_, urlContacts);
487 Json::Value jsonContacts(Json::arrayValue);
488 for (const auto& contact : info_->contacts->getContacts()) {
489 auto jsonContact = contact.second.toJson();
490 jsonContact["uri"] = contact.first.toString();
491 jsonContacts.append(std::move(jsonContact));
492 }
493 sendDeviceRequest(std::make_shared<Request>(
494 *Manager::instance().ioContext(),
495 urlContacts,
496 jsonContacts,
497 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
498 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
499 if (!this_) return;
500 JAMI_DEBUG("[Account {}] [Auth] Got contact sync request callback with status code={}",
501 this_->accountId_, response.status_code);
502 if (response.status_code >= 200 && response.status_code < 300) {
503 try {
504 JAMI_WARNING("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
505 if (not json.isArray()) {
506 JAMI_ERROR("[Auth] Unable to parse server response: not an array");
507 } else {
508 for (unsigned i = 0, n = json.size(); i < n; i++) {
509 const auto& e = json[i];
510 Contact contact(e);
511 this_->info_->contacts
512 ->updateContact(dht::InfoHash {e["uri"].asString()}, contact);
513 }
514 this_->info_->contacts->saveContacts();
515 }
516 } catch (const std::exception& e) {
517 JAMI_ERROR("Error when iterating contact list: {}", e.what());
518 }
519 } else if (response.status_code == 401)
520 this_->authError(TokenScope::Device);
521
522 this_->clearRequest(response.request);
523 },
524 logger_));
525
526 JAMI_WARNING("[Account {}] [Auth] syncDevices {}", accountId_, urlDevices);
527 sendDeviceRequest(std::make_shared<Request>(
528 *Manager::instance().ioContext(),
529 urlDevices,
530 [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
531 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
532 if (!this_) return;
533 JAMI_DEBUG("[Account {}] [Auth] Got request callback with status code={}", this_->accountId_, response.status_code);
534 if (response.status_code >= 200 && response.status_code < 300) {
535 try {
536 JAMI_LOG("[Account {}] [Auth] Got server response: {} bytes", this_->accountId_, response.body.size());
537 if (not json.isArray()) {
538 JAMI_ERROR("[Auth] Unable to parse server response: not an array");
539 } else {
540 for (unsigned i = 0, n = json.size(); i < n; i++) {
541 const auto& e = json[i];
542 const bool revoked = e["revoked"].asBool();
543 dht::PkId deviceId(e["deviceId"].asString());
544 if(!deviceId){
545 continue;
546 }
547 if (!revoked) {
548 this_->info_->contacts->foundAccountDevice(deviceId,
549 e["alias"].asString(),
550 clock::now());
551 }
552 else {
553 this_->info_->contacts->removeAccountDevice(deviceId);
554 }
555 }
556 }
557 } catch (const std::exception& e) {
558 JAMI_ERROR("Error when iterating device list: {}", e.what());
559 }
560 } else if (response.status_code == 401)
561 this_->authError(TokenScope::Device);
562
563 this_->clearRequest(response.request);
564 },
565 logger_));
566}
567
568void
569ServerAccountManager::syncBlueprintConfig(SyncBlueprintCallback onSuccess)
570{
571 auto syncBlueprintCallback = std::make_shared<SyncBlueprintCallback>(onSuccess);
572 const std::string urlBlueprints = managerHostname_ + PATH_BLUEPRINT;
573 JAMI_DEBUG("[Auth] Synchronize blueprint configuration {}", urlBlueprints);
574 sendDeviceRequest(std::make_shared<Request>(
575 *Manager::instance().ioContext(),
576 urlBlueprints,
577 [syncBlueprintCallback, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
578 JAMI_DEBUG("[Auth] Got sync request callback with status code={}", response.status_code);
579 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
580 if (!this_) return;
581 if (response.status_code >= 200 && response.status_code < 300) {
582 try {
583 std::map<std::string, std::string> config;
584 for (auto itr = json.begin(); itr != json.end(); ++itr) {
585 const auto& name = itr.name();
586 config.emplace(name, itr->asString());
587 }
588 (*syncBlueprintCallback)(config);
589 } catch (const std::exception& e) {
590 JAMI_ERROR("Error when iterating blueprint config json: {}", e.what());
591 }
592 } else if (response.status_code == 401)
593 this_->authError(TokenScope::Device);
594 this_->clearRequest(response.request);
595 },
596 logger_));
597}
598
599bool
600ServerAccountManager::revokeDevice(const std::string& device,
601 std::string_view scheme, const std::string& password,
603{
604 if (not info_ || scheme != fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD) {
605 if (cb)
606 cb(RevokeDeviceResult::ERROR_CREDENTIALS);
607 return false;
608 }
609 const std::string url = managerHostname_ + PATH_DEVICE + "/" + device;
610 JAMI_WARNING("[Revoke] Revoking device of {} at {}", info_->username, url);
611 auto request = std::make_shared<Request>(
612 *Manager::instance().ioContext(),
613 url,
614 [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
615 JAMI_DEBUG("[Revoke] Got request callback with status code={}", response.status_code);
616 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
617 if (!this_) return;
618 if (response.status_code >= 200 && response.status_code < 300) {
619 try {
620 JAMI_WARNING("[Revoke] Got server response");
621 if (json["errorDetails"].empty()) {
622 if (cb)
623 cb(RevokeDeviceResult::SUCCESS);
624 this_->syncDevices(); // this will remove the devices from the known devices
625 }
626 } catch (const std::exception& e) {
627 JAMI_ERROR("Error when loading device list: {}", e.what());
628 }
629 } else if (cb)
630 cb(RevokeDeviceResult::ERROR_NETWORK);
631 this_->clearRequest(response.request);
632 },
633 logger_);
634 request->set_method(restinio::http_method_delete());
635 sendAccountRequest(request, password);
636 return true;
637}
638
639void
640ServerAccountManager::registerName(const std::string& name, std::string_view scheme, const std::string&, RegistrationCallback cb)
641{
642 cb(NameDirectory::RegistrationResponse::unsupported, name);
643}
644
645bool
646ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
647{
648 const std::string url = managerHostname_ + PATH_SEARCH + "?queryString=" + query;
649 JAMI_WARNING("[Search] Searching user {} at {}", query, url);
650 sendDeviceRequest(std::make_shared<Request>(
651 *Manager::instance().ioContext(),
652 url,
653 [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
654 JAMI_DEBUG("[Search] Got request callback with status code={}", response.status_code);
655 auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
656 if (!this_) return;
657 if (response.status_code >= 200 && response.status_code < 300) {
658 try {
659 const auto& profiles = json["profiles"];
660 Json::Value::ArrayIndex rcount = profiles.size();
661 std::vector<std::map<std::string, std::string>> results;
662 results.reserve(rcount);
663 JAMI_WARNING("[Search] Got server response: {} bytes", response.body.size());
664 for (Json::Value::ArrayIndex i = 0; i < rcount; i++) {
665 const auto& ruser = profiles[i];
666 std::map<std::string, std::string> user;
667 for (const auto& member : ruser.getMemberNames()) {
668 const auto& rmember = ruser[member];
669 if (rmember.isString())
670 user[member] = rmember.asString();
671 }
672 results.emplace_back(std::move(user));
673 }
674 if (cb)
675 cb(results, SearchResponse::found);
676 } catch (const std::exception& e) {
677 JAMI_ERROR("[Search] Error during search: {}", e.what());
678 }
679 } else {
680 if (response.status_code == 401)
681 this_->authError(TokenScope::Device);
682 if (cb)
683 cb({}, SearchResponse::error);
684 }
685 this_->clearRequest(response.request);
686 },
687 logger_));
688 return true;
689}
690
691} // namespace jami
NameDirectory::RegistrationCallback RegistrationCallback
const std::string accountId_
OnChangeCallback onChange_
CertRequest buildRequest(PrivateKey fDeviceKey)
std::unique_ptr< AccountInfo > info_
static std::shared_ptr< dht::Value > parseAnnounce(const std::string &announceBase64, const std::string &accountId, const std::string &deviceSha1, const std::string &deviceSha256)
std::shared_future< std::shared_ptr< dht::crypto::PrivateKey > > PrivateKey
std::function< void(RevokeDeviceResult)> RevokeDeviceCallback
std::function< void(AuthError error, const std::string &message)> AuthFailureCallback
NameDirectory::SearchCallback SearchCallback
std::function< void(const AccountInfo &info, const std::map< std::string, std::string > &config, std::string &&receipt, std::vector< uint8_t > &&receipt_signature)> AuthSuccessCallback
Level-driven logging class that support printf and C++ stream logging fashions.
Definition logger.h:83
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
std::shared_ptr< asio::io_context > ioContext() const
Definition manager.cpp:1712
static NameDirectory & instance()
void initAuthentication(PrivateKey request, std::string deviceName, std::unique_ptr< AccountCredentials > credentials, AuthSuccessCallback onSuccess, AuthFailureCallback onFailure, const OnChangeCallback &onChange) override
std::function< void(const std::map< std::string, std::string > &config)> SyncBlueprintCallback
ServerAccountManager(const std::string &accountId, const std::filesystem::path &path, const std::string &managerHostname, const std::string &nameServer)
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:226
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
Definition manager.h:42
std::string asString(bytes const &_b)
Converts byte array to a string containing the same (binary) data.
Definition CommonData.h:101
std::vector< uint8_t > decode(std::string_view str)
Definition base64.cpp:47
constexpr std::string_view PATH_CONVERSATIONS
constexpr std::string_view PATH_BLUEPRINT
void emitSignal(Args... args)
Definition ring_signal.h:64
dht::http::Request Request
constexpr std::string_view PATH_CONVERSATIONS_REQUESTS
const std::string & userAgent()
constexpr std::string_view PATH_CONTACTS
constexpr std::string_view PATH_DEVICES
constexpr std::string_view PATH_DEVICE
constexpr std::string_view PATH_SEARCH
static constexpr const char URI[]
std::vector< std::map< std::string, std::string > > getContacts(const std::string &accountId)
#define JAMI_PATH_AUTH
#define JAMI_PATH_LOGIN
std::map< std::string, ConvInfo > c