user_service.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. import 'dart:typed_data';
  2. import 'package:dio/dio.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_sodium/flutter_sodium.dart';
  5. import 'package:logging/logging.dart';
  6. import 'package:photos/core/configuration.dart';
  7. import 'package:photos/core/event_bus.dart';
  8. import 'package:photos/core/network.dart';
  9. import 'package:photos/db/public_keys_db.dart';
  10. import 'package:photos/events/two_factor_status_change_event.dart';
  11. import 'package:photos/events/user_details_changed_event.dart';
  12. import 'package:photos/models/key_attributes.dart';
  13. import 'package:photos/models/key_gen_result.dart';
  14. import 'package:photos/models/public_key.dart';
  15. import 'package:photos/models/sessions.dart';
  16. import 'package:photos/models/set_keys_request.dart';
  17. import 'package:photos/models/set_recovery_key_request.dart';
  18. import 'package:photos/models/user_details.dart';
  19. import 'package:photos/ui/account/login_page.dart';
  20. import 'package:photos/ui/account/ott_verification_page.dart';
  21. import 'package:photos/ui/account/password_entry_page.dart';
  22. import 'package:photos/ui/account/password_reentry_page.dart';
  23. import 'package:photos/ui/account/two_factor_authentication_page.dart';
  24. import 'package:photos/ui/account/two_factor_recovery_page.dart';
  25. import 'package:photos/ui/account/two_factor_setup_page.dart';
  26. import 'package:photos/utils/crypto_util.dart';
  27. import 'package:photos/utils/dialog_util.dart';
  28. import 'package:photos/utils/navigation_util.dart';
  29. import 'package:photos/utils/toast_util.dart';
  30. class UserService {
  31. final _dio = Network.instance.getDio();
  32. final _logger = Logger((UserService).toString());
  33. final _config = Configuration.instance;
  34. ValueNotifier<String> emailValueNotifier;
  35. UserService._privateConstructor();
  36. static final UserService instance = UserService._privateConstructor();
  37. Future<void> init() async {
  38. emailValueNotifier =
  39. ValueNotifier<String>(Configuration.instance.getEmail());
  40. }
  41. Future<void> getOtt(
  42. BuildContext context,
  43. String email, {
  44. bool isChangeEmail = false,
  45. bool isCreateAccountScreen = false,
  46. }) async {
  47. final dialog = createProgressDialog(context, "Please wait...");
  48. await dialog.show();
  49. try {
  50. final response = await _dio.get(
  51. _config.getHttpEndpoint() + "/users/ott",
  52. queryParameters: {
  53. "email": email,
  54. "purpose": isChangeEmail ? "change" : ""
  55. },
  56. );
  57. await dialog.hide();
  58. if (response != null && response.statusCode == 200) {
  59. Navigator.of(context).push(
  60. MaterialPageRoute(
  61. builder: (BuildContext context) {
  62. return OTTVerificationPage(
  63. email,
  64. isChangeEmail: isChangeEmail,
  65. isCreateAccountScreen: isCreateAccountScreen,
  66. );
  67. },
  68. ),
  69. );
  70. return;
  71. }
  72. showGenericErrorDialog(context);
  73. } on DioError catch (e) {
  74. await dialog.hide();
  75. _logger.info(e);
  76. if (e.response != null && e.response.statusCode == 403) {
  77. showErrorDialog(context, "Oops", "This email is already in use");
  78. } else {
  79. showGenericErrorDialog(context);
  80. }
  81. } catch (e) {
  82. await dialog.hide();
  83. _logger.severe(e);
  84. showGenericErrorDialog(context);
  85. }
  86. }
  87. Future<String> getPublicKey(String email) async {
  88. try {
  89. final response = await _dio.get(
  90. _config.getHttpEndpoint() + "/users/public-key",
  91. queryParameters: {"email": email},
  92. options: Options(
  93. headers: {
  94. "X-Auth-Token": _config.getToken(),
  95. },
  96. ),
  97. );
  98. final publicKey = response.data["publicKey"];
  99. await PublicKeysDB.instance.setKey(PublicKey(email, publicKey));
  100. return publicKey;
  101. } on DioError catch (e) {
  102. _logger.info(e);
  103. return null;
  104. }
  105. }
  106. Future<UserDetails> getUserDetailsV2({bool memoryCount = true}) async {
  107. try {
  108. final response = await _dio.get(
  109. _config.getHttpEndpoint() +
  110. "/users/details/v2?memoryCount=$memoryCount",
  111. queryParameters: {
  112. "memoryCount": memoryCount,
  113. },
  114. options: Options(
  115. headers: {
  116. "X-Auth-Token": _config.getToken(),
  117. },
  118. ),
  119. );
  120. return UserDetails.fromMap(response.data);
  121. } on DioError catch (e) {
  122. _logger.info(e);
  123. rethrow;
  124. }
  125. }
  126. Future<Sessions> getActiveSessions() async {
  127. try {
  128. final response = await _dio.get(
  129. _config.getHttpEndpoint() + "/users/sessions",
  130. options: Options(
  131. headers: {
  132. "X-Auth-Token": _config.getToken(),
  133. },
  134. ),
  135. );
  136. return Sessions.fromMap(response.data);
  137. } on DioError catch (e) {
  138. _logger.info(e);
  139. rethrow;
  140. }
  141. }
  142. Future<void> terminateSession(String token) async {
  143. try {
  144. await _dio.delete(
  145. _config.getHttpEndpoint() + "/users/session",
  146. options: Options(
  147. headers: {
  148. "X-Auth-Token": _config.getToken(),
  149. },
  150. ),
  151. queryParameters: {
  152. "token": token,
  153. },
  154. );
  155. } on DioError catch (e) {
  156. _logger.info(e);
  157. rethrow;
  158. }
  159. }
  160. Future<void> leaveFamilyPlan() async {
  161. try {
  162. await _dio.delete(
  163. _config.getHttpEndpoint() + "/family/leave",
  164. options: Options(
  165. headers: {
  166. "X-Auth-Token": _config.getToken(),
  167. },
  168. ),
  169. );
  170. } on DioError catch (e) {
  171. _logger.warning('failed to leave family plan', e);
  172. rethrow;
  173. }
  174. }
  175. Future<void> logout(BuildContext context) async {
  176. final dialog = createProgressDialog(context, "Logging out...");
  177. await dialog.show();
  178. try {
  179. final response = await _dio.post(
  180. _config.getHttpEndpoint() + "/users/logout",
  181. options: Options(
  182. headers: {
  183. "X-Auth-Token": _config.getToken(),
  184. },
  185. ),
  186. );
  187. if (response != null && response.statusCode == 200) {
  188. await Configuration.instance.logout();
  189. await dialog.hide();
  190. Navigator.of(context).popUntil((route) => route.isFirst);
  191. } else {
  192. throw Exception("Log out action failed");
  193. }
  194. } catch (e) {
  195. _logger.severe(e);
  196. await dialog.hide();
  197. showGenericErrorDialog(context);
  198. }
  199. }
  200. Future<void> verifyEmail(BuildContext context, String ott) async {
  201. final dialog = createProgressDialog(context, "Please wait...");
  202. await dialog.show();
  203. try {
  204. final response = await _dio.post(
  205. _config.getHttpEndpoint() + "/users/verify-email",
  206. data: {
  207. "email": _config.getEmail(),
  208. "ott": ott,
  209. },
  210. );
  211. await dialog.hide();
  212. if (response != null && response.statusCode == 200) {
  213. showShortToast(context, "Email verification successful!");
  214. Widget page;
  215. final String twoFASessionID = response.data["twoFactorSessionID"];
  216. if (twoFASessionID != null && twoFASessionID.isNotEmpty) {
  217. page = TwoFactorAuthenticationPage(twoFASessionID);
  218. } else {
  219. await _saveConfiguration(response);
  220. if (Configuration.instance.getEncryptedToken() != null) {
  221. page = const PasswordReentryPage();
  222. } else {
  223. page = const PasswordEntryPage();
  224. }
  225. }
  226. Navigator.of(context).pushAndRemoveUntil(
  227. MaterialPageRoute(
  228. builder: (BuildContext context) {
  229. return page;
  230. },
  231. ),
  232. (route) => route.isFirst,
  233. );
  234. } else {
  235. // should never reach here
  236. throw Exception("unexpected response during email verification");
  237. }
  238. } on DioError catch (e) {
  239. _logger.info(e);
  240. await dialog.hide();
  241. if (e.response != null && e.response.statusCode == 410) {
  242. await showErrorDialog(
  243. context,
  244. "Oops",
  245. "Your verification code has expired",
  246. );
  247. Navigator.of(context).pop();
  248. } else {
  249. showErrorDialog(
  250. context,
  251. "Incorrect code",
  252. "Sorry, the code you've entered is incorrect",
  253. );
  254. }
  255. } catch (e) {
  256. await dialog.hide();
  257. _logger.severe(e);
  258. showErrorDialog(context, "Oops", "Verification failed, please try again");
  259. }
  260. }
  261. Future<void> setEmail(String email) async {
  262. await _config.setEmail(email);
  263. emailValueNotifier.value = email ?? "";
  264. }
  265. Future<void> changeEmail(
  266. BuildContext context,
  267. String email,
  268. String ott,
  269. ) async {
  270. final dialog = createProgressDialog(context, "Please wait...");
  271. await dialog.show();
  272. try {
  273. final response = await _dio.post(
  274. _config.getHttpEndpoint() + "/users/change-email",
  275. data: {
  276. "email": email,
  277. "ott": ott,
  278. },
  279. options: Options(
  280. headers: {
  281. "X-Auth-Token": _config.getToken(),
  282. },
  283. ),
  284. );
  285. await dialog.hide();
  286. if (response != null && response.statusCode == 200) {
  287. showToast(context, "Email changed to " + email);
  288. await setEmail(email);
  289. Navigator.of(context).popUntil((route) => route.isFirst);
  290. Bus.instance.fire(UserDetailsChangedEvent());
  291. return;
  292. }
  293. showErrorDialog(context, "Oops", "Verification failed, please try again");
  294. } on DioError catch (e) {
  295. await dialog.hide();
  296. if (e.response != null && e.response.statusCode == 403) {
  297. showErrorDialog(context, "Oops", "This email is already in use");
  298. } else {
  299. showErrorDialog(
  300. context,
  301. "Incorrect code",
  302. "Authentication failed, please try again",
  303. );
  304. }
  305. } catch (e) {
  306. await dialog.hide();
  307. _logger.severe(e);
  308. showErrorDialog(context, "Oops", "Verification failed, please try again");
  309. }
  310. }
  311. Future<void> setAttributes(KeyGenResult result) async {
  312. try {
  313. final name = _config.getName();
  314. await _dio.put(
  315. _config.getHttpEndpoint() + "/users/attributes",
  316. data: {
  317. "name": name,
  318. "keyAttributes": result.keyAttributes.toMap(),
  319. },
  320. options: Options(
  321. headers: {
  322. "X-Auth-Token": _config.getToken(),
  323. },
  324. ),
  325. );
  326. await _config.setKey(result.privateKeyAttributes.key);
  327. await _config.setSecretKey(result.privateKeyAttributes.secretKey);
  328. await _config.setKeyAttributes(result.keyAttributes);
  329. } catch (e) {
  330. _logger.severe(e);
  331. rethrow;
  332. }
  333. }
  334. Future<void> updateKeyAttributes(KeyAttributes keyAttributes) async {
  335. try {
  336. final setKeyRequest = SetKeysRequest(
  337. kekSalt: keyAttributes.kekSalt,
  338. encryptedKey: keyAttributes.encryptedKey,
  339. keyDecryptionNonce: keyAttributes.keyDecryptionNonce,
  340. memLimit: keyAttributes.memLimit,
  341. opsLimit: keyAttributes.opsLimit,
  342. );
  343. await _dio.put(
  344. _config.getHttpEndpoint() + "/users/keys",
  345. data: setKeyRequest.toMap(),
  346. options: Options(
  347. headers: {
  348. "X-Auth-Token": _config.getToken(),
  349. },
  350. ),
  351. );
  352. await _config.setKeyAttributes(keyAttributes);
  353. } catch (e) {
  354. _logger.severe(e);
  355. rethrow;
  356. }
  357. }
  358. Future<void> setRecoveryKey(KeyAttributes keyAttributes) async {
  359. try {
  360. final setRecoveryKeyRequest = SetRecoveryKeyRequest(
  361. keyAttributes.masterKeyEncryptedWithRecoveryKey,
  362. keyAttributes.masterKeyDecryptionNonce,
  363. keyAttributes.recoveryKeyEncryptedWithMasterKey,
  364. keyAttributes.recoveryKeyDecryptionNonce,
  365. );
  366. await _dio.put(
  367. _config.getHttpEndpoint() + "/users/recovery-key",
  368. data: setRecoveryKeyRequest.toMap(),
  369. options: Options(
  370. headers: {
  371. "X-Auth-Token": _config.getToken(),
  372. },
  373. ),
  374. );
  375. await _config.setKeyAttributes(keyAttributes);
  376. } catch (e) {
  377. _logger.severe(e);
  378. rethrow;
  379. }
  380. }
  381. Future<void> verifyTwoFactor(
  382. BuildContext context,
  383. String sessionID,
  384. String code,
  385. ) async {
  386. final dialog = createProgressDialog(context, "Authenticating...");
  387. await dialog.show();
  388. try {
  389. final response = await _dio.post(
  390. _config.getHttpEndpoint() + "/users/two-factor/verify",
  391. data: {
  392. "sessionID": sessionID,
  393. "code": code,
  394. },
  395. );
  396. await dialog.hide();
  397. if (response != null && response.statusCode == 200) {
  398. showToast(context, "Authentication successful!");
  399. await _saveConfiguration(response);
  400. Navigator.of(context).pushAndRemoveUntil(
  401. MaterialPageRoute(
  402. builder: (BuildContext context) {
  403. return const PasswordReentryPage();
  404. },
  405. ),
  406. (route) => route.isFirst,
  407. );
  408. }
  409. } on DioError catch (e) {
  410. await dialog.hide();
  411. _logger.severe(e);
  412. if (e.response != null && e.response.statusCode == 404) {
  413. showToast(context, "Session expired");
  414. Navigator.of(context).pushAndRemoveUntil(
  415. MaterialPageRoute(
  416. builder: (BuildContext context) {
  417. return const LoginPage();
  418. },
  419. ),
  420. (route) => route.isFirst,
  421. );
  422. } else {
  423. showErrorDialog(
  424. context,
  425. "Incorrect code",
  426. "Authentication failed, please try again",
  427. );
  428. }
  429. } catch (e) {
  430. await dialog.hide();
  431. _logger.severe(e);
  432. showErrorDialog(
  433. context,
  434. "Oops",
  435. "Authentication failed, please try again",
  436. );
  437. }
  438. }
  439. Future<void> recoverTwoFactor(BuildContext context, String sessionID) async {
  440. final dialog = createProgressDialog(context, "Please wait...");
  441. await dialog.show();
  442. try {
  443. final response = await _dio.get(
  444. _config.getHttpEndpoint() + "/users/two-factor/recover",
  445. queryParameters: {
  446. "sessionID": sessionID,
  447. },
  448. );
  449. if (response != null && response.statusCode == 200) {
  450. Navigator.of(context).pushAndRemoveUntil(
  451. MaterialPageRoute(
  452. builder: (BuildContext context) {
  453. return TwoFactorRecoveryPage(
  454. sessionID,
  455. response.data["encryptedSecret"],
  456. response.data["secretDecryptionNonce"],
  457. );
  458. },
  459. ),
  460. (route) => route.isFirst,
  461. );
  462. }
  463. } on DioError catch (e) {
  464. _logger.severe(e);
  465. if (e.response != null && e.response.statusCode == 404) {
  466. showToast(context, "Session expired");
  467. Navigator.of(context).pushAndRemoveUntil(
  468. MaterialPageRoute(
  469. builder: (BuildContext context) {
  470. return const LoginPage();
  471. },
  472. ),
  473. (route) => route.isFirst,
  474. );
  475. } else {
  476. showErrorDialog(
  477. context,
  478. "Oops",
  479. "Something went wrong, please try again",
  480. );
  481. }
  482. } catch (e) {
  483. _logger.severe(e);
  484. showErrorDialog(
  485. context,
  486. "Oops",
  487. "Something went wrong, please try again",
  488. );
  489. } finally {
  490. await dialog.hide();
  491. }
  492. }
  493. Future<void> removeTwoFactor(
  494. BuildContext context,
  495. String sessionID,
  496. String recoveryKey,
  497. String encryptedSecret,
  498. String secretDecryptionNonce,
  499. ) async {
  500. final dialog = createProgressDialog(context, "Please wait...");
  501. await dialog.show();
  502. String secret;
  503. try {
  504. secret = Sodium.bin2base64(
  505. await CryptoUtil.decrypt(
  506. Sodium.base642bin(encryptedSecret),
  507. Sodium.hex2bin(recoveryKey.trim()),
  508. Sodium.base642bin(secretDecryptionNonce),
  509. ),
  510. );
  511. } catch (e) {
  512. await dialog.hide();
  513. showErrorDialog(
  514. context,
  515. "Incorrect recovery key",
  516. "The recovery key you entered is incorrect",
  517. );
  518. return;
  519. }
  520. try {
  521. final response = await _dio.post(
  522. _config.getHttpEndpoint() + "/users/two-factor/remove",
  523. data: {
  524. "sessionID": sessionID,
  525. "secret": secret,
  526. },
  527. );
  528. if (response != null && response.statusCode == 200) {
  529. showShortToast(context, "Two-factor authentication successfully reset");
  530. await _saveConfiguration(response);
  531. Navigator.of(context).pushAndRemoveUntil(
  532. MaterialPageRoute(
  533. builder: (BuildContext context) {
  534. return const PasswordReentryPage();
  535. },
  536. ),
  537. (route) => route.isFirst,
  538. );
  539. }
  540. } on DioError catch (e) {
  541. _logger.severe(e);
  542. if (e.response != null && e.response.statusCode == 404) {
  543. showToast(context, "Session expired");
  544. Navigator.of(context).pushAndRemoveUntil(
  545. MaterialPageRoute(
  546. builder: (BuildContext context) {
  547. return const LoginPage();
  548. },
  549. ),
  550. (route) => route.isFirst,
  551. );
  552. } else {
  553. showErrorDialog(
  554. context,
  555. "Oops",
  556. "Something went wrong, please try again",
  557. );
  558. }
  559. } catch (e) {
  560. _logger.severe(e);
  561. showErrorDialog(
  562. context,
  563. "Oops",
  564. "Something went wrong, please try again",
  565. );
  566. } finally {
  567. await dialog.hide();
  568. }
  569. }
  570. Future<void> setupTwoFactor(BuildContext context) async {
  571. final dialog = createProgressDialog(context, "Please wait...");
  572. await dialog.show();
  573. try {
  574. final response = await _dio.post(
  575. _config.getHttpEndpoint() + "/users/two-factor/setup",
  576. options: Options(
  577. headers: {
  578. "X-Auth-Token": _config.getToken(),
  579. },
  580. ),
  581. );
  582. await dialog.hide();
  583. routeToPage(
  584. context,
  585. TwoFactorSetupPage(
  586. response.data["secretCode"],
  587. response.data["qrCode"],
  588. ),
  589. );
  590. } catch (e, s) {
  591. await dialog.hide();
  592. _logger.severe(e, s);
  593. rethrow;
  594. }
  595. }
  596. Future<bool> enableTwoFactor(
  597. BuildContext context,
  598. String secret,
  599. String code,
  600. ) async {
  601. Uint8List recoveryKey;
  602. try {
  603. recoveryKey = await getOrCreateRecoveryKey(context);
  604. } catch (e) {
  605. showGenericErrorDialog(context);
  606. return false;
  607. }
  608. final dialog = createProgressDialog(context, "Verifying...");
  609. await dialog.show();
  610. final encryptionResult =
  611. CryptoUtil.encryptSync(Sodium.base642bin(secret), recoveryKey);
  612. try {
  613. await _dio.post(
  614. _config.getHttpEndpoint() + "/users/two-factor/enable",
  615. data: {
  616. "code": code,
  617. "encryptedTwoFactorSecret":
  618. Sodium.bin2base64(encryptionResult.encryptedData),
  619. "twoFactorSecretDecryptionNonce":
  620. Sodium.bin2base64(encryptionResult.nonce),
  621. },
  622. options: Options(
  623. headers: {
  624. "X-Auth-Token": _config.getToken(),
  625. },
  626. ),
  627. );
  628. await dialog.hide();
  629. Navigator.pop(context);
  630. Bus.instance.fire(TwoFactorStatusChangeEvent(true));
  631. return true;
  632. } catch (e, s) {
  633. await dialog.hide();
  634. _logger.severe(e, s);
  635. if (e is DioError) {
  636. if (e.response != null && e.response.statusCode == 401) {
  637. showErrorDialog(
  638. context,
  639. "Incorrect code",
  640. "Please verify the code you have entered",
  641. );
  642. return false;
  643. }
  644. }
  645. showErrorDialog(
  646. context,
  647. "Something went wrong",
  648. "Please contact support if the problem persists",
  649. );
  650. }
  651. return false;
  652. }
  653. Future<void> disableTwoFactor(BuildContext context) async {
  654. final dialog =
  655. createProgressDialog(context, "Disabling two-factor authentication...");
  656. await dialog.show();
  657. try {
  658. await _dio.post(
  659. _config.getHttpEndpoint() + "/users/two-factor/disable",
  660. options: Options(
  661. headers: {
  662. "X-Auth-Token": _config.getToken(),
  663. },
  664. ),
  665. );
  666. Bus.instance.fire(TwoFactorStatusChangeEvent(false));
  667. await dialog.hide();
  668. showToast(context, "Two-factor authentication has been disabled");
  669. } catch (e, s) {
  670. await dialog.hide();
  671. _logger.severe(e, s);
  672. showErrorDialog(
  673. context,
  674. "Something went wrong",
  675. "Please contact support if the problem persists",
  676. );
  677. }
  678. }
  679. Future<bool> fetchTwoFactorStatus() async {
  680. try {
  681. final response = await _dio.get(
  682. _config.getHttpEndpoint() + "/users/two-factor/status",
  683. options: Options(
  684. headers: {
  685. "X-Auth-Token": _config.getToken(),
  686. },
  687. ),
  688. );
  689. return response.data["status"];
  690. } catch (e, s) {
  691. _logger.severe(e, s);
  692. rethrow;
  693. }
  694. }
  695. Future<Uint8List> getOrCreateRecoveryKey(BuildContext context) async {
  696. final encryptedRecoveryKey =
  697. _config.getKeyAttributes().recoveryKeyEncryptedWithMasterKey;
  698. if (encryptedRecoveryKey == null || encryptedRecoveryKey.isEmpty) {
  699. final dialog = createProgressDialog(context, "Please wait...");
  700. await dialog.show();
  701. try {
  702. final keyAttributes = await _config.createNewRecoveryKey();
  703. await setRecoveryKey(keyAttributes);
  704. await dialog.hide();
  705. } catch (e, s) {
  706. await dialog.hide();
  707. _logger.severe(e, s);
  708. rethrow;
  709. }
  710. }
  711. final recoveryKey = _config.getRecoveryKey();
  712. return recoveryKey;
  713. }
  714. Future<String> getPaymentToken() async {
  715. try {
  716. var response = await _dio.get(
  717. "${_config.getHttpEndpoint()}/users/payment-token",
  718. options: Options(
  719. headers: {
  720. "X-Auth-Token": _config.getToken(),
  721. },
  722. ),
  723. );
  724. if (response != null && response.statusCode == 200) {
  725. return response.data["paymentToken"];
  726. } else {
  727. throw Exception("non 200 ok response");
  728. }
  729. } catch (e, s) {
  730. _logger.severe(e, s);
  731. return null;
  732. }
  733. }
  734. Future<String> getFamiliesToken() async {
  735. try {
  736. var response = await _dio.get(
  737. "${_config.getHttpEndpoint()}/users/families-token",
  738. options: Options(
  739. headers: {
  740. "X-Auth-Token": _config.getToken(),
  741. },
  742. ),
  743. );
  744. if (response != null && response.statusCode == 200) {
  745. return response.data["familiesToken"];
  746. } else {
  747. throw Exception("non 200 ok response");
  748. }
  749. } catch (e, s) {
  750. _logger.severe("failed to fetch families token", e, s);
  751. rethrow;
  752. }
  753. }
  754. Future<void> _saveConfiguration(Response response) async {
  755. await Configuration.instance.setUserID(response.data["id"]);
  756. if (response.data["encryptedToken"] != null) {
  757. await Configuration.instance
  758. .setEncryptedToken(response.data["encryptedToken"]);
  759. await Configuration.instance.setKeyAttributes(
  760. KeyAttributes.fromMap(response.data["keyAttributes"]),
  761. );
  762. } else {
  763. await Configuration.instance.setToken(response.data["token"]);
  764. }
  765. }
  766. }