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