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

feat: introduce a new 'register' package with consolidated code #508

Draft
wants to merge 19 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .github/workflows/at_libraries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
- at_onboarding_cli
- at_commons
- at_utils
- at_register
steps:
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2

Expand Down
2 changes: 1 addition & 1 deletion packages/at_onboarding_cli/bin/register_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import 'package:at_onboarding_cli/src/register_cli/register.dart'

Future<void> main(List<String> args) async {
await register_cli.main(args);
}
}
8 changes: 4 additions & 4 deletions packages/at_onboarding_cli/example/get_cram_key.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import 'package:args/args.dart';
import 'package:at_onboarding_cli/src/util/onboarding_util.dart';
import 'package:at_register/at_register.dart';

import 'util/custom_arg_parser.dart';

Future<void> main(args) async {
final argResults = CustomArgParser(getArgParser()).parse(args);

// this step sends an OTP to the registered email
await OnboardingUtil().requestAuthenticationOtp(
await RegistrarApiAccessor().requestAuthenticationOtp(
argResults['atsign']); // requires a registered atsign

// the following step validates the email that was sent in the above step
String? verificationCode = OnboardingUtil().getVerificationCodeFromUser();
String cramKey = await OnboardingUtil().getCramKey(argResults['atsign'],
String? verificationCode = ApiUtil.readCliVerificationCode();
String cramKey = await RegistrarApiAccessor().getCramKey(argResults['atsign'],
verificationCode); // verification code received on the registered email

print('Your cram key is: $cramKey');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import 'package:encrypt/encrypt.dart';
import 'package:zxing2/qrcode.dart';
import 'package:image/image.dart';
import 'package:path/path.dart' as path;
import 'package:at_register/at_register.dart';

import '../util/home_directory_util.dart';
import '../util/onboarding_util.dart';

///class containing service that can onboard/activate/authenticate @signs
class AtOnboardingServiceImpl implements AtOnboardingService {
Expand Down Expand Up @@ -97,7 +97,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService {

// get cram_secret from either from AtOnboardingPreference
// or fetch from the registrar using verification code sent to email
atOnboardingPreference.cramSecret ??= await OnboardingUtil()
atOnboardingPreference.cramSecret ??= await RegistrarApiAccessor()
.getCramUsingOtp(_atSign, atOnboardingPreference.registrarUrl);
if (atOnboardingPreference.cramSecret == null) {
logger.info('Root Server address is ${atOnboardingPreference.rootDomain}:'
Expand Down
204 changes: 34 additions & 170 deletions packages/at_onboarding_cli/lib/src/register_cli/register.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import 'dart:collection';
import 'dart:io';

import 'package:args/args.dart';
import 'package:at_client/at_client.dart';
import 'package:at_onboarding_cli/src/activate_cli/activate_cli.dart'
as activate_cli;
import 'package:at_onboarding_cli/src/util/api_call_status.dart';
import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart';
import 'package:at_onboarding_cli/src/util/register_api_result.dart';
import 'package:at_onboarding_cli/src/util/register_api_task.dart';
import 'package:at_onboarding_cli/src/register_cli/registration_flow.dart';
import 'package:at_utils/at_logger.dart';

import '../util/onboarding_util.dart';
import '../util/registrar_api_constants.dart';
import 'package:at_register/at_register.dart';

///Class containing logic to register a free atsign to email provided
///through [args] by utilizing methods defined in [RegisterUtil]
///Requires List<String> args containing the following arguments: email
class Register {
Future<void> main(List<String> args) async {
Map<String, String> params = HashMap<String, String>();
OnboardingUtil registerUtil = OnboardingUtil();
RegisterParams registerParams = RegisterParams();
RegistrarApiAccessor registrarApiAccessor = RegistrarApiAccessor();

final argParser = ArgParser()
..addOption('email',
Expand All @@ -40,188 +34,58 @@ class Register {
if (!argResults.wasParsed('email')) {
stderr.writeln(
'[Unable to run Register CLI] Please enter your email address'
'\n[Usage] dart run register.dart -e [email protected]\n[Options]\n${argParser.usage}');
'\n[Usage] dart run bin/register.dart -e [email protected]\n[Options]\n${argParser.usage}');
exit(6);
}

if (registerUtil.validateEmail(argResults['email'])) {
params['email'] = argResults['email'];
if (ApiUtil.enforceEmailRegex(argResults['email'])) {
registerParams.email = argResults['email'];
} else {
stderr.writeln(
'[Unable to run Register CLI] You have entered an invalid email address. Check your email address and try again.');
exit(7);
}

//set the following parameter to RegisterApiConstants.apiHostStaging
//to use the staging environment
params['authority'] = RegistrarApiConstants.apiHostProd;

//create stream of tasks each of type [RegisterApiTask] and then
// call start on the stream of tasks
await RegistrationFlow(params, registerUtil)
.add(GetFreeAtsign())
.add(RegisterAtsign())
.add(ValidateOtp())
.start();

activate_cli.main(['-a', params['atsign']!, '-c', params['cramkey']!]);
}
}

///class that handles multiple tasks of type [RegisterApiTask]
///Initialized with a params map that needs to be populated with - email and api host address
///[add] method can be used to add tasks[RegisterApiTask] to the [processFlow]
///[start] needs to be called after all required tasks are added to the [processFlow]
class RegistrationFlow {
List<RegisterApiTask> processFlow = [];
RegisterApiResult result = RegisterApiResult();
late OnboardingUtil registerUtil;
Map<String, String> params;

RegistrationFlow(this.params, this.registerUtil);

RegistrationFlow add(RegisterApiTask task) {
processFlow.add(task);
return this;
}

Future<void> start() async {
for (RegisterApiTask task in processFlow) {
task.init(params, registerUtil);
if (RegistrarApiConstants.isDebugMode) {
print('Current Task: $task [params=$params]\n');
}
result = await task.run();
if (result.apiCallStatus == ApiCallStatus.retry) {
while (
task.shouldRetry() && result.apiCallStatus == ApiCallStatus.retry) {
result = await task.run();
task.retryCount++;
}
}
if (result.apiCallStatus == ApiCallStatus.success) {
params.addAll(result.data);
} else {
throw AtOnboardingException(result.exceptionMessage);
}
}
}
}

///This is a [RegisterApiTask] that fetches a free atsign
///throws [AtException] with concerned message which was encountered in the
///HTTP GET/POST request
class GetFreeAtsign extends RegisterApiTask {
@override
Future<RegisterApiResult> run() async {
stdout
.writeln('[Information] Getting your randomly generated free atSign…');
try {
List<String> atsignList =
await registerUtil.getFreeAtSigns(authority: params['authority']!);
result.data['atsign'] = atsignList[0];
stdout.writeln('[Information] Your new atSign is **@${atsignList[0]}**');
result.apiCallStatus = ApiCallStatus.success;
} on Exception catch (e) {
result.exceptionMessage = e.toString();
result.apiCallStatus =
shouldRetry() ? ApiCallStatus.retry : ApiCallStatus.failure;
}
GetFreeAtsign getFreeAtsignTask = GetFreeAtsign(
apiAccessorInstance: registrarApiAccessor, allowRetry: true);

return result;
}
}
RegisterAtsign registerAtsignTask =
RegisterAtsign(apiAccessorInstance: registrarApiAccessor, allowRetry: true);

///This is a [RegisterApiTask] that registers a free atsign fetched in
///[GetFreeAtsign] to the email provided as args
///throws [AtException] with concerned message which was encountered in the
///HTTP GET/POST request
class RegisterAtsign extends RegisterApiTask {
@override
Future<RegisterApiResult> run() async {
stdout.writeln(
'[Information] Sending verification code to: ${params['email']}');
try {
result.data['otpSent'] = (await registerUtil.registerAtSign(
params['atsign']!, params['email']!,
authority: params['authority']!))
.toString();
stdout.writeln(
'[Information] Verification code sent to: ${params['email']}');
result.apiCallStatus = ApiCallStatus.success;
} on Exception catch (e) {
result.exceptionMessage = e.toString();
result.apiCallStatus =
shouldRetry() ? ApiCallStatus.retry : ApiCallStatus.failure;
}
return result;
}
}
ValidateOtp validateOtpTask =
ValidateOtp(apiAccessorInstance: registrarApiAccessor, allowRetry: true);

///This is a [RegisterApiTask] that validates the otp which was sent as a part
///of [RegisterAtsign] to email provided in args
///throws [AtException] with concerned message which was encountered in the
///HTTP GET/POST request
class ValidateOtp extends RegisterApiTask {
@override
void init(Map<String, String> params, OnboardingUtil registerUtil) {
params['confirmation'] = 'false';
this.params = params;
this.registerUtil = registerUtil;
result.data = HashMap<String, String>();
}
// create a queue of tasks each of type [RegisterTask] and then
// call start on the RegistrationFlow object
await RegistrationFlow(registerParams)
.add(getFreeAtsignTask)
.add(registerAtsignTask)
.add(validateOtpTask)
.start();

@override
Future<RegisterApiResult> run() async {
if (params['otp'] == null) {
params['otp'] = registerUtil.getVerificationCodeFromUser();
}
stdout.writeln('[Information] Validating your verification code...');
try {
String apiResponse = await registerUtil.validateOtp(
params['atsign']!, params['email']!, params['otp']!,
confirmation: params['confirmation']!,
authority: params['authority']!);
if (apiResponse == 'retry') {
stderr.writeln(
'[Unable to proceed] The verification code you entered is either invalid or expired.\n'
' Check your verification code and try again.');
params['otp'] = registerUtil.getVerificationCodeFromUser();
result.apiCallStatus = ApiCallStatus.retry;
result.exceptionMessage =
'Incorrect otp entered 3 times. Max retries reached.';
} else if (apiResponse == 'follow-up') {
params.update('confirmation', (value) => 'true');
result.data['otp'] = params['otp'];
result.apiCallStatus = ApiCallStatus.retry;
} else if (apiResponse.startsWith("@")) {
result.data['cramkey'] = apiResponse.split(":")[1];
stdout.writeln(
'[Information] Your cram secret: ${result.data['cramkey']}');
stdout.writeln('[Success] Your atSign **@${params['atsign']}** has been'
' successfully registered to ${params['email']}');
result.apiCallStatus = ApiCallStatus.success;
}
} on Exception catch (e) {
result.exceptionMessage = e.toString();
result.apiCallStatus =
shouldRetry() ? ApiCallStatus.retry : ApiCallStatus.failure;
}
return result;
activate_cli
.main(['-a', registerParams.atsign!, '-c', registerParams.cram!]);
}
}

Future<void> main(List<String> args) async {
Register register = Register();
AtSignLogger.root_level = 'severe';
AtSignLogger.root_level = 'info';
try {
await register.main(args);
} on MaximumAtsignQuotaException {
stdout.writeln(
'[Unable to proceed] This email address already has 10 free atSigns associated with it.\n'
'To register a new atSign to this email address, please log into the dashboard \'my.atsign.com/login\'.\n'
'Remove at least 1 atSign from your account and then try again.\n'
'Alternatively, you can retry this process with a different email address.');
exit(0);
} on FormatException catch (e) {
if (e.toString().contains('Missing argument')) {
stderr.writeln(
'[Unable to run Register CLI] Please re-run with your email address');
stderr
.writeln('Usage: \'dart run register_cli.dart -e [email protected]\'');
stderr.writeln(
'Usage: \'dart run bin/register_cli.dart -e [email protected]\'');
exit(1);
} else if (e.toString().contains('Could not find an option or flag')) {
stderr
Expand All @@ -237,11 +101,11 @@ Future<void> main(List<String> args) async {
stderr.writeln('Cause: $e');
exit(3);
}
} on AtOnboardingException catch (e) {
} on AtException catch (e) {
stderr.writeln(
'[Error] Failed getting an atsign. It looks like something went wrong on our side.\n'
'Please try again or contact [email protected], quoting the text displayed below.');
stderr.writeln('Cause: $e ExceptionType:${e.runtimeType}');
stderr.writeln('Cause: ${e.message} ExceptionType:${e.runtimeType}');
exit(4);
} on Exception catch (e) {
if (e
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:at_register/at_register.dart';

/// Processes tasks of type [RegisterTask]
/// Initialized with a params map that needs to be populated with - email and api host address
/// [add] method can be used to add tasks[RegisterTask] to the [processQueue]
/// [start] needs to be called after all required tasks are added to the [processQueue]
class RegistrationFlow {
List<RegisterTask> processQueue = [];
RegisterTaskResult _result = RegisterTaskResult();
late RegisterParams params;
String defaultExceptionMessage = 'Could not complete the task. Please retry';

RegistrationFlow(this.params);

RegistrationFlow add(RegisterTask task) {
processQueue.add(task);
return this;
}

Future<RegisterTaskResult> start() async {
for (RegisterTask task in processQueue) {
try {
_result = await task.run(params);
task.logger.finer('Attempt: ${task.retryCount} | params[$params]');
task.logger.finer('Result: $_result');

while (_result.apiCallStatus == ApiCallStatus.retry &&
task.shouldRetry()) {
_result = await task.retry(params);
task.logger.finer('Attempt: ${task.retryCount} | params[$params]');
task.logger.finer('Result: $_result');
}
if (_result.apiCallStatus == ApiCallStatus.success) {
params.addFromJson(_result.data);
} else {
throw _result.exception ??
AtRegisterException('${task.name}: $defaultExceptionMessage');
}
} on MaximumAtsignQuotaException {
rethrow;
} on ExhaustedVerificationCodeRetriesException {
rethrow;
} on InvalidVerificationCodeException {
rethrow;
} on AtRegisterException {
rethrow;
} on Exception catch (e) {
throw AtRegisterException(e.toString());
}
}
return _result;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:core';

import 'package:at_chops/at_chops.dart';
import 'package:at_client/at_client.dart';
import 'package:at_onboarding_cli/src/util/registrar_api_constants.dart';
import 'package:at_register/at_register.dart';

class AtOnboardingPreference extends AtClientPreference {
/// specify path of .atKeysFile containing encryption keys
Expand All @@ -28,7 +28,7 @@ class AtOnboardingPreference extends AtClientPreference {
bool skipSync = false;

/// the hostName of the registrar which will be used to activate the atsign
String registrarUrl = RegistrarApiConstants.apiHostProd;
String registrarUrl = RegistrarConstants.apiHostProd;

String? appName;

Expand Down
Loading