Skip to content
This repository was archived by the owner on Aug 5, 2025. It is now read-only.
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@email.com\n[Options]\n${argParser.usage}');
'\n[Usage] dart run bin/register.dart -e email@email.com\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@email.com\'');
stderr.writeln(
'Usage: \'dart run bin/register_cli.dart -e email@email.com\'');
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 support@atsign.com, 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