mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-10 19:03:12 +00:00
feat: Allow refreshing device token for Server API
This commit is contained in:
parent
0fb898873c
commit
8f42d89457
|
@ -569,6 +569,14 @@
|
||||||
"description": "The device {} will no longer have access to the server.",
|
"description": "The device {} will no longer have access to the server.",
|
||||||
"yes": "Revoke",
|
"yes": "Revoke",
|
||||||
"no": "Cancel"
|
"no": "Cancel"
|
||||||
|
},
|
||||||
|
"refresh_token_alert": {
|
||||||
|
"header": "Refresh access token?",
|
||||||
|
"description": "A new token will be generated for this device to connect to the server.",
|
||||||
|
"yes": "Confirm",
|
||||||
|
"no": "Cancel",
|
||||||
|
"failed_to_refresh_token": "Couldn't refresh the token. Please try again.",
|
||||||
|
"success_refresh_token": "Token refreshed successfully."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recovery_key": {
|
"recovery_key": {
|
||||||
|
|
|
@ -550,6 +550,41 @@ class ServerApi extends GraphQLApiMap
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<GenericResult<String>> refreshDeviceApiToken() async {
|
||||||
|
GenericResult<String> token;
|
||||||
|
QueryResult<Mutation$RefreshDeviceApiToken> response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final GraphQLClient client = await getClient();
|
||||||
|
|
||||||
|
final mutation = Options$Mutation$RefreshDeviceApiToken();
|
||||||
|
response = await client.mutate$RefreshDeviceApiToken(
|
||||||
|
mutation,
|
||||||
|
);
|
||||||
|
if (response.hasException) {
|
||||||
|
print(response.exception.toString());
|
||||||
|
token = GenericResult<String>(
|
||||||
|
success: false,
|
||||||
|
data: '',
|
||||||
|
message: response.exception.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
token = GenericResult<String>(
|
||||||
|
success: true,
|
||||||
|
data: response.parsedData!.api.refreshDeviceApiToken.token!,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
token = GenericResult<String>(
|
||||||
|
success: false,
|
||||||
|
data: '',
|
||||||
|
message: e.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> isHttpServerWorking() async => (await getApiVersion()) != null;
|
Future<bool> isHttpServerWorking() async => (await getApiVersion()) != null;
|
||||||
|
|
||||||
Future<GenericResult<String>> authorizeDevice(
|
Future<GenericResult<String>> authorizeDevice(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
@ -32,6 +33,10 @@ class TokensBloc extends Bloc<TokensEvent, TokensState> {
|
||||||
on<ServerSelectedForProviderToken>(
|
on<ServerSelectedForProviderToken>(
|
||||||
connectServerToProviderToken,
|
connectServerToProviderToken,
|
||||||
);
|
);
|
||||||
|
on<RefreshServerApiTokenEvent>(
|
||||||
|
refreshServerApiToken,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
|
||||||
add(const RevalidateTokens());
|
add(const RevalidateTokens());
|
||||||
|
|
||||||
|
@ -208,6 +213,33 @@ class TokensBloc extends Bloc<TokensEvent, TokensState> {
|
||||||
await getIt<ResourcesModel>().updateServerByDomain(newServerData);
|
await getIt<ResourcesModel>().updateServerByDomain(newServerData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> refreshServerApiToken(
|
||||||
|
final RefreshServerApiTokenEvent event,
|
||||||
|
final Emitter<TokensState> emit,
|
||||||
|
) async {
|
||||||
|
final (success, newToken) =
|
||||||
|
await getIt<ApiConnectionRepository>().refreshDeviceToken();
|
||||||
|
if (!success) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'devices.refresh_token_alert.failed_to_refresh_token'.tr(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Server serverDetails = state.servers.first;
|
||||||
|
final hostingDetails = serverDetails.hostingDetails.copyWith(
|
||||||
|
apiToken: newToken,
|
||||||
|
);
|
||||||
|
await getIt<ResourcesModel>().updateServerByDomain(
|
||||||
|
Server(
|
||||||
|
hostingDetails: hostingDetails,
|
||||||
|
domain: serverDetails.domain,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'devices.refresh_token_alert.success_refresh_token'.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_resourcesModelSubscription.cancel();
|
_resourcesModelSubscription.cancel();
|
||||||
|
|
|
@ -34,3 +34,10 @@ class ServerSelectedForProviderToken extends TokensEvent {
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [providerServer, server, serverProviderCredential];
|
List<Object> get props => [providerServer, server, serverProviderCredential];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RefreshServerApiTokenEvent extends TokensEvent {
|
||||||
|
const RefreshServerApiTokenEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
|
@ -257,6 +257,16 @@ class ApiConnectionRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> refreshDeviceToken() async {
|
||||||
|
final GenericResult<String> result = await api.refreshDeviceApiToken();
|
||||||
|
_apiData.devices.invalidate();
|
||||||
|
if (result.success) {
|
||||||
|
return (true, result.data);
|
||||||
|
} else {
|
||||||
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_dataStream.close();
|
_dataStream.close();
|
||||||
_connectionStatusStream.close();
|
_connectionStatusStream.close();
|
||||||
|
|
|
@ -50,16 +50,17 @@ class ServerHostingDetails {
|
||||||
final DateTime? startTime,
|
final DateTime? startTime,
|
||||||
final String? serverLocation,
|
final String? serverLocation,
|
||||||
final String? serverType,
|
final String? serverType,
|
||||||
|
final String? apiToken,
|
||||||
}) =>
|
}) =>
|
||||||
ServerHostingDetails(
|
ServerHostingDetails(
|
||||||
startTime: startTime ?? this.startTime,
|
startTime: startTime ?? this.startTime,
|
||||||
serverLocation: serverLocation ?? this.serverLocation,
|
serverLocation: serverLocation ?? this.serverLocation,
|
||||||
serverType: serverType ?? this.serverType,
|
serverType: serverType ?? this.serverType,
|
||||||
|
apiToken: apiToken ?? this.apiToken,
|
||||||
createTime: createTime,
|
createTime: createTime,
|
||||||
id: id,
|
id: id,
|
||||||
ip4: ip4,
|
ip4: ip4,
|
||||||
volume: volume,
|
volume: volume,
|
||||||
apiToken: apiToken,
|
|
||||||
provider: provider,
|
provider: provider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,8 @@ abstract class AppThemeFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<ColorScheme?> _getDynamicColors(
|
static Future<ColorScheme?> _getDynamicColors(
|
||||||
final Brightness brightness) async {
|
final Brightness brightness,
|
||||||
|
) async {
|
||||||
List<Color> extractAdditionalColours(final ColorScheme scheme) => [
|
List<Color> extractAdditionalColours(final ColorScheme scheme) => [
|
||||||
scheme.surface,
|
scheme.surface,
|
||||||
scheme.surfaceDim,
|
scheme.surfaceDim,
|
||||||
|
@ -65,7 +66,9 @@ abstract class AppThemeFactory {
|
||||||
];
|
];
|
||||||
|
|
||||||
ColorScheme insertAdditionalColours(
|
ColorScheme insertAdditionalColours(
|
||||||
final ColorScheme scheme, final List<Color> additionalColours) =>
|
final ColorScheme scheme,
|
||||||
|
final List<Color> additionalColours,
|
||||||
|
) =>
|
||||||
scheme.copyWith(
|
scheme.copyWith(
|
||||||
surface: additionalColours[0],
|
surface: additionalColours[0],
|
||||||
surfaceDim: additionalColours[1],
|
surfaceDim: additionalColours[1],
|
||||||
|
@ -88,7 +91,9 @@ abstract class AppThemeFactory {
|
||||||
}
|
}
|
||||||
// Workaround as dynamic_color doesn't add new color roles
|
// Workaround as dynamic_color doesn't add new color roles
|
||||||
final fromSeed = ColorScheme.fromSeed(
|
final fromSeed = ColorScheme.fromSeed(
|
||||||
seedColor: colorScheme.primary, brightness: brightness);
|
seedColor: colorScheme.primary,
|
||||||
|
brightness: brightness,
|
||||||
|
);
|
||||||
final additionalColours = extractAdditionalColours(fromSeed);
|
final additionalColours = extractAdditionalColours(fromSeed);
|
||||||
final updatedColorScheme =
|
final updatedColorScheme =
|
||||||
insertAdditionalColours(colorScheme, additionalColours);
|
insertAdditionalColours(colorScheme, additionalColours);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/tokens/tokens_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
|
@ -121,7 +122,7 @@ class _DeviceTile extends StatelessWidget {
|
||||||
.tr(args: [DateFormat.yMMMMd().format(device.date)]),
|
.tr(args: [DateFormat.yMMMMd().format(device.date)]),
|
||||||
),
|
),
|
||||||
onTap: device.isCaller
|
onTap: device.isCaller
|
||||||
? null
|
? () => _showTokenRefreshDialog(context, device)
|
||||||
: () => _showConfirmationDialog(context, device),
|
: () => _showConfirmationDialog(context, device),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -172,4 +173,54 @@ class _DeviceTile extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future _showTokenRefreshDialog(
|
||||||
|
final BuildContext context,
|
||||||
|
final ApiToken device,
|
||||||
|
) =>
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final context) => AlertDialog(
|
||||||
|
title: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.update_outlined),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'devices.refresh_token_alert.header'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'devices.refresh_token_alert.description'
|
||||||
|
.tr(args: [device.name]),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text('devices.refresh_token_alert.no'.tr()),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text('devices.refresh_token_alert.yes'.tr()),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<TokensBloc>()
|
||||||
|
.add(const RefreshServerApiTokenEvent());
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue