Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Npt desktop onboarding #1564

Merged
merged 8 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/dart/npt_flutter/lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart' show dotenv;

class Constants {
static bool dotenvLoaded = false;
static Future<void> loadDotenv() async {
if (dotenvLoaded) return;
try {
await dotenv.load();
dotenvLoaded = true;
} catch (_) {
dotenvLoaded = false;
}
}

static String? get namespace => 'noports';
// TODO: issue & secure API key properly
static String? get appAPIKey => 'asdf';

static Future<String?> get appAPIKey async {
await loadDotenv();
return dotenv.env["APP_API_KEY"];
}

static const pngIconDark = 'assets/noports-icon64-dark.png';
static const icoIconDark = 'assets/noports-icon64-dark.ico';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ class FavoriteBloc extends LoggingBloc<FavoriteEvent, FavoritesState> {

void clearAll() => emit(const FavoritesInitial());

FutureOr<void> _onLoad(
FavoriteLoadEvent event, Emitter<FavoritesState> emit) async {
FutureOr<void> _onLoad(FavoriteLoadEvent event, Emitter<FavoritesState> emit) async {
emit(const FavoritesLoading());

Map<String, Favorite>? favs;
Expand All @@ -35,8 +34,7 @@ class FavoriteBloc extends LoggingBloc<FavoriteEvent, FavoritesState> {
emit(FavoritesLoaded(favs.values));
}

FutureOr<void> _onAdd(
FavoriteAddEvent event, Emitter<FavoritesState> emit) async {
FutureOr<void> _onAdd(FavoriteAddEvent event, Emitter<FavoritesState> emit) async {
if (state is! FavoritesLoaded) {
return;
}
Expand All @@ -49,17 +47,13 @@ class FavoriteBloc extends LoggingBloc<FavoriteEvent, FavoritesState> {
} catch (_) {}
}

FutureOr<void> _onRemove(
FavoriteRemoveEvent event, Emitter<FavoritesState> emit) async {
FutureOr<void> _onRemove(FavoriteRemoveEvent event, Emitter<FavoritesState> emit) async {
if (state is! FavoritesLoaded) {
return;
}

emit(FavoritesLoaded(
(state as FavoritesLoaded)
.favorites
.toSet()
.difference(event.toRemove.toSet()),
(state as FavoritesLoaded).favorites.toSet().difference(event.toRemove.toSet()),
));

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import 'dart:convert';
import 'dart:io';

import 'package:at_auth/at_auth.dart';
import 'package:at_onboarding_flutter/at_onboarding_flutter.dart' hide Response;
import 'package:at_onboarding_flutter/at_onboarding_services.dart';
// ignore: implementation_imports
import 'package:at_onboarding_flutter/src/utils/at_onboarding_response_status.dart';
import 'package:at_server_status/at_server_status.dart';
import 'package:http/http.dart';
import 'package:http/io_client.dart';

// Type returned from a method below
export 'package:at_onboarding_flutter/src/utils/at_onboarding_response_status.dart';

const apiBase = '/api/app/v3';

enum NoPortsActivateApiEndpoints {
login('$apiBase/authenticate/atsign'),
validate('$apiBase/authenticate/atsign/activate');

final String path;
const NoPortsActivateApiEndpoints(this.path);
}

class ActivateUtil {
final String registrarUrl;
final String apiKey;
late final IOClient _http;

ActivateUtil({required this.registrarUrl, required this.apiKey}) {
var innerClient = HttpClient();
innerClient.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
_http = IOClient();
}

Future<Response> registrarApiRequest(NoPortsActivateApiEndpoints endpoint, Map<String, String?> data) async {
Uri url = Uri.https(registrarUrl, endpoint.path);

return _http.post(
url,
body: jsonEncode(data),
headers: <String, String>{
'Authorization': apiKey,
'Content-Type': 'application/json',
},
);
}

Future<({String? cramkey, String? errorMessage})> verifyActivation(
{required String atsign, required String otp}) async {
var res = await registrarApiRequest(
NoPortsActivateApiEndpoints.validate,
{
'atsign': atsign,
'otp': otp,
},
);
if (res.statusCode != 200) {
return (
errorMessage: AtOnboardingLocalizations.current.error_server_unavailable,
cramkey: null,
);
}
var payload = jsonDecode(res.body);
if (payload["message"] != "Verified") {
// The toString is for typesafety & to prevent unexpected crashes
return (errorMessage: payload["message"].toString(), cramkey: null);
}
String cramkey = payload["cramkey"]?.split(':').last ?? '';
return (cramkey: cramkey, errorMessage: null);
}

Future<AtOnboardingResult> onboardFromCramKey({
required String atsign,
required String cramkey,
required AtOnboardingConfig config,
}) async {
try {
atsign = atsign.startsWith('@') ? atsign : '@$atsign';
OnboardingService onboardingService = OnboardingService.getInstance();
bool isExist = await onboardingService.isExistingAtsign(atsign);
if (isExist) {
return AtOnboardingResult.error(
message: AtOnboardingLocalizations.current.error_atSign_activated,
);
}

//Delay for waiting for ServerStatus change to teapot when activating an atsign
await Future.delayed(const Duration(seconds: 10));

config.atClientPreference.cramSecret = cramkey;
onboardingService.setAtClientPreference = config.atClientPreference;

onboardingService.setAtsign = atsign;
AtOnboardingRequest req = AtOnboardingRequest(atsign);
var res = await onboardingService.onboard(
cramSecret: cramkey,
atOnboardingRequest: req,
);

if (res) {
int round = 1;
ServerStatus? atSignStatus = await onboardingService.checkAtSignServerStatus(atsign);
while (atSignStatus != ServerStatus.activated) {
if (round > 10) {
break;
}
await Future.delayed(const Duration(seconds: 3));
round++;
atSignStatus = await onboardingService.checkAtSignServerStatus(atsign);
}

if (atSignStatus == ServerStatus.teapot) {
return AtOnboardingResult.error(
message: AtOnboardingLocalizations.current.msg_atSign_unreachable,
);
} else if (atSignStatus == ServerStatus.activated) {
return AtOnboardingResult.success(atsign: atsign);
}
}

return AtOnboardingResult.error(message: AtOnboardingLocalizations.current.error_authenticated_failed);
} catch (e) {
if (e == AtOnboardingResponseStatus.authFailed) {
return AtOnboardingResult.error(
message: AtOnboardingLocalizations.current.error_authenticated_failed,
);
} else if (e == AtOnboardingResponseStatus.serverNotReached) {
return AtOnboardingResult.error(
message: AtOnboardingLocalizations.current.msg_atSign_unreachable,
);
} else if (e == AtOnboardingResponseStatus.timeOut) {
return AtOnboardingResult.error(
message: AtOnboardingLocalizations.current.msg_response_time_out,
);
}
return AtOnboardingResult.error(message: e.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'dart:async';
import 'package:at_onboarding_flutter/at_onboarding_flutter.dart';
import 'package:at_onboarding_flutter/at_onboarding_services.dart' show AtKeysFileUploadService, FileUploadStatus;
import 'package:at_server_status/at_server_status.dart';

// These types are returned from methods in this class so exports are provided for ease of use
export 'package:at_onboarding_flutter/at_onboarding_services.dart' show FileUploadStatus;
export 'package:at_server_status/at_server_status.dart' show AtStatus;

class NoPortsOnboardingUtil {
/// The upload service will be created when the first time [uploadAtKeysFile] is called
AtKeysFileUploadService? _uploadService;
AtServerStatus? _atServerStatus;
AtOnboardingConfig config;
NoPortsOnboardingUtil(this.config);

/// A method to check whether an atSign has been activated or not
Future<AtStatus> atServerStatus(String atSign) async {
_atServerStatus ??=
AtStatusImpl(rootUrl: config.atClientPreference.rootDomain, rootPort: config.atClientPreference.rootPort);
return _atServerStatus!.get(atSign);
}

/// Upload an atKeys file, returning a stream with the progress so we can update the ui accordingly.
/// Example implementation:
/// https://github.com/atsign-foundation/at_widgets/blob/b4006854fa93c21eeb5bcea41044787bdf0f6f32/packages/at_onboarding_flutter/lib/src/screen/at_onboarding_home_screen.dart#L659
Stream<FileUploadStatus> uploadAtKeysFile(String? atSign) {
_uploadService ??= AtKeysFileUploadService(config: config);
return _uploadService!.uploadKeyFile(atSign);
}

// TODO: implement APKAM onboarding
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Future<void> postOnboard(String atSign, String rootDomain) async {
status: OnboardingStatus.onboarded,
);
// Start loading application data in the background as soon as we have an atClient
App.navState.currentContext?.read<FavoriteBloc>().add(const FavoriteLoadEvent());
App.navState.currentContext?.read<ProfileListBloc>().add(const ProfileListLoadEvent());
App.navState.currentContext?.read<SettingsBloc>().add(const SettingsLoadEvent());
App.navState.currentContext?.read<FavoriteBloc>().add(const FavoriteLoadEvent());
}
Loading