From 31be961dd0856345f3b7e3a642edb7c05648dfb5 Mon Sep 17 00:00:00 2001
From: NaiJi <naijiworld@protonmail.com>
Date: Tue, 10 May 2022 02:16:36 +0300
Subject: [PATCH] Implement server endpoints for tokens

get /auth/recovery_token
post /auth/recovery_token
post /auth/recovery_token/use
post /auth/new_device/authorize
post /auth/new_device
delete /auth/new_device
get /auth/tokens
post /auth/tokens
delete /auth/tokens
---
 lib/logic/api_maps/server.dart                | 241 ++++++++++++++++++
 lib/logic/models/api_token.dart               |  20 ++
 lib/logic/models/api_token.g.dart             |  13 +
 lib/logic/models/device_token.dart            |  17 ++
 lib/logic/models/device_token.g.dart          |  12 +
 lib/logic/models/recovery_token_status.dart   |  23 ++
 lib/logic/models/recovery_token_status.g.dart |  19 ++
 7 files changed, 345 insertions(+)
 create mode 100644 lib/logic/models/api_token.dart
 create mode 100644 lib/logic/models/api_token.g.dart
 create mode 100644 lib/logic/models/device_token.dart
 create mode 100644 lib/logic/models/device_token.g.dart
 create mode 100644 lib/logic/models/recovery_token_status.dart
 create mode 100644 lib/logic/models/recovery_token_status.g.dart

diff --git a/lib/logic/api_maps/server.dart b/lib/logic/api_maps/server.dart
index 6302611e..7554f46b 100644
--- a/lib/logic/api_maps/server.dart
+++ b/lib/logic/api_maps/server.dart
@@ -5,9 +5,12 @@ import 'dart:io';
 import 'package:dio/dio.dart';
 import 'package:selfprivacy/config/get_it_config.dart';
 import 'package:selfprivacy/logic/common_enum/common_enum.dart';
+import 'package:selfprivacy/logic/models/api_token.dart';
 import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
 import 'package:selfprivacy/logic/models/backblaze_bucket.dart';
 import 'package:selfprivacy/logic/models/backup.dart';
+import 'package:selfprivacy/logic/models/recovery_token_status.dart';
+import 'package:selfprivacy/logic/models/device_token.dart';
 import 'package:selfprivacy/logic/models/timezone_settings.dart';
 import 'package:selfprivacy/logic/models/user.dart';
 
@@ -431,6 +434,244 @@ class ServerApi extends ApiMap {
         .split(')')[0]
         .replaceAll('"', '');
   }
+
+  Future<ApiResponse<RecoveryTokenStatus>> getRecoveryTokenStatus() async {
+    var client = await getClient();
+    Response response = await client.get(
+      '/auth/recovery_token',
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null
+              ? response.data.fromJson(response.data)
+              : null);
+    }
+
+    return ApiResponse(
+        statusCode: HttpStatus.internalServerError,
+        data: RecoveryTokenStatus(exists: false, valid: false));
+  }
+
+  Future<ApiResponse<String>> generateRecoveryToken(
+      DateTime expiration, int uses) async {
+    var client = await getClient();
+    Response response = await client.post(
+      '/auth/recovery_token',
+      data: {
+        'expiration': expiration.toIso8601String(),
+        'uses': uses,
+      },
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null ? response.data["token"] : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<String>> useRecoveryToken(DeviceToken token) async {
+    var client = await getClient();
+    Response response = await client.post(
+      '/auth/recovery_token/use',
+      data: {
+        'token': token.token,
+        'device': token.device,
+      },
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null ? response.data["token"] : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<String>> authorizeDevice(DeviceToken token) async {
+    var client = await getClient();
+    Response response = await client.post(
+      '/auth/new_device/authorize',
+      data: {
+        'token': token.token,
+        'device': token.device,
+      },
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null ? response.data : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<String>> createDeviceToken() async {
+    var client = await getClient();
+    Response response = await client.post(
+      '/auth/new_device',
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null ? response.data["token"] : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<String>> deleteDeviceToken() async {
+    var client = await getClient();
+    Response response = await client.delete(
+      '/auth/new_device',
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: response.data != null ? response.data : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<List<ApiToken>>> getApiTokens() async {
+    var client = await getClient();
+    Response response = await client.get(
+      '/auth/tokens',
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: (response.data != null)
+              ? response.data
+                  .map<ApiToken>((e) => ApiToken.fromJson(e))
+                  .toList()
+              : []);
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: []);
+  }
+
+  Future<ApiResponse<String>> refreshCurrentApiToken() async {
+    var client = await getClient();
+    Response response = await client.post(
+      '/auth/tokens',
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(
+          statusCode: response.statusCode!,
+          data: (response.data != null) ? response.data["token"] : '');
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: '');
+  }
+
+  Future<ApiResponse<void>> deleteApiToken(String device) async {
+    var client = await getClient();
+    Response response = await client.delete(
+      '/auth/tokens',
+      data: {
+        'device': device,
+      },
+      options: Options(
+          contentType: 'application/json',
+          receiveDataWhenStatusError: true,
+          followRedirects: false,
+          validateStatus: (status) {
+            return (status != null) &&
+                (status < HttpStatus.internalServerError);
+          }),
+    );
+    client.close();
+
+    if (response.statusCode != null) {
+      return ApiResponse(statusCode: response.statusCode!, data: null);
+    }
+
+    return ApiResponse(statusCode: HttpStatus.internalServerError, data: null);
+  }
 }
 
 extension UrlServerExt on ServiceTypes {
diff --git a/lib/logic/models/api_token.dart b/lib/logic/models/api_token.dart
new file mode 100644
index 00000000..25556399
--- /dev/null
+++ b/lib/logic/models/api_token.dart
@@ -0,0 +1,20 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'api_token.g.dart';
+
+@JsonSerializable()
+class ApiToken {
+  ApiToken({
+    required this.name,
+    required this.date,
+    required this.is_caller,
+  });
+
+  final String name;
+  final DateTime date;
+  final bool is_caller;
+
+  factory ApiToken.fromJson(Map<String, dynamic> json) =>
+      _$ApiTokenFromJson(json);
+}
diff --git a/lib/logic/models/api_token.g.dart b/lib/logic/models/api_token.g.dart
new file mode 100644
index 00000000..c009f58b
--- /dev/null
+++ b/lib/logic/models/api_token.g.dart
@@ -0,0 +1,13 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'api_token.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ApiToken _$ApiTokenFromJson(Map<String, dynamic> json) => ApiToken(
+      name: json['name'] as String,
+      date: DateTime.parse(json['date'] as String),
+      is_caller: json['is_caller'] as bool,
+    );
diff --git a/lib/logic/models/device_token.dart b/lib/logic/models/device_token.dart
new file mode 100644
index 00000000..d53299c5
--- /dev/null
+++ b/lib/logic/models/device_token.dart
@@ -0,0 +1,17 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'device_token.g.dart';
+
+@JsonSerializable()
+class DeviceToken {
+  DeviceToken({
+    required this.device,
+    required this.token,
+  });
+
+  final String device;
+  final String token;
+
+  factory DeviceToken.fromJson(Map<String, dynamic> json) =>
+      _$DeviceTokenFromJson(json);
+}
diff --git a/lib/logic/models/device_token.g.dart b/lib/logic/models/device_token.g.dart
new file mode 100644
index 00000000..efe976c5
--- /dev/null
+++ b/lib/logic/models/device_token.g.dart
@@ -0,0 +1,12 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'device_token.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+DeviceToken _$DeviceTokenFromJson(Map<String, dynamic> json) => DeviceToken(
+      device: json['device'] as String,
+      token: json['token'] as String,
+    );
diff --git a/lib/logic/models/recovery_token_status.dart b/lib/logic/models/recovery_token_status.dart
new file mode 100644
index 00000000..9c94b41a
--- /dev/null
+++ b/lib/logic/models/recovery_token_status.dart
@@ -0,0 +1,23 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'recovery_token_status.g.dart';
+
+@JsonSerializable()
+class RecoveryTokenStatus {
+  RecoveryTokenStatus({
+    required this.exists,
+    required this.valid,
+    this.date,
+    this.expiration,
+    this.uses_left,
+  });
+
+  final bool exists;
+  final DateTime? date;
+  final DateTime? expiration;
+  final int? uses_left;
+  final bool valid;
+
+  factory RecoveryTokenStatus.fromJson(Map<String, dynamic> json) =>
+      _$RecoveryTokenStatusFromJson(json);
+}
diff --git a/lib/logic/models/recovery_token_status.g.dart b/lib/logic/models/recovery_token_status.g.dart
new file mode 100644
index 00000000..3f213364
--- /dev/null
+++ b/lib/logic/models/recovery_token_status.g.dart
@@ -0,0 +1,19 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'recovery_token_status.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+RecoveryTokenStatus _$RecoveryTokenStatusFromJson(Map<String, dynamic> json) =>
+    RecoveryTokenStatus(
+      exists: json['exists'] as bool,
+      valid: json['valid'] as bool,
+      date:
+          json['date'] == null ? null : DateTime.parse(json['date'] as String),
+      expiration: json['expiration'] == null
+          ? null
+          : DateTime.parse(json['expiration'] as String),
+      uses_left: json['uses_left'] as int?,
+    );