diff --git a/packages/at_secondary_server/CHANGELOG.md b/packages/at_secondary_server/CHANGELOG.md index 4a5017e56..79d1e39dc 100644 --- a/packages/at_secondary_server/CHANGELOG.md +++ b/packages/at_secondary_server/CHANGELOG.md @@ -2,6 +2,8 @@ - fix: Implement notify ephemeral changes - Send notification with value without caching the key on receiver's secondary server - feat: Implement AtRateLimiter to limit the enrollment requests on a particular connection - fix: Upgraded at_commons to 3.0.56 +- fix: Enable client to set OTP expiry via OTP verb +- fix: Prevent reuse of OTP - fix: Modify sync_progressive_verb_handler to filter responses on enrolled namespaces if authenticated via APKAM ## 3.0.35 - chore: Upgraded at_persistence_secondary_server to 3.0.57 for memory optimization in commit log diff --git a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart index 5e0c52b50..e39f9f05d 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart @@ -152,4 +152,25 @@ abstract class AbstractVerbHandler implements VerbHandler { return false; } } + + + /// This function checks the validity of a provided OTP. + /// It returns true if the OTP is valid; otherwise, it returns false. + /// If the OTP is not found in the keystore, it also returns false. + /// + /// Additionally, this function removes the OTP from the keystore to prevent its reuse. + Future isOTPValid(String? otp) async { + if (otp == null) { + return false; + } + String otpKey = + 'private:${otp.toLowerCase()}${AtSecondaryServerImpl.getInstance().currentAtSign}'; + AtData otpAtData; + try { + otpAtData = await keyStore.get(otpKey); + } on KeyNotFoundException { + return false; + } + return SecondaryUtil.isActiveKey(otpAtData); + } } diff --git a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart index 31b43f930..215f8104c 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart @@ -8,7 +8,6 @@ import 'package:at_secondary/src/server/at_secondary_config.dart'; import 'package:at_secondary/src/constants/enroll_constants.dart'; import 'package:at_secondary/src/enroll/enroll_datastore_value.dart'; import 'package:at_secondary/src/utils/notification_util.dart'; -import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart'; import 'package:at_server_spec/at_server_spec.dart'; import 'package:at_server_spec/at_verb_spec.dart'; import 'package:meta/meta.dart'; @@ -50,15 +49,15 @@ class EnrollVerbHandler extends AbstractVerbHandler { try { // Ensure that enrollParams are present for all enroll operation // Exclude operation 'list' which does not have enrollParams - if (verbParams[enrollParams] == null) { + if (verbParams[AtConstants.enrollParams] == null) { if (operation != 'list') { logger.severe( - 'Enroll params is empty | EnrollParams: ${verbParams[enrollParams]}'); + 'Enroll params is empty | EnrollParams: ${verbParams[AtConstants.enrollParams]}'); throw IllegalArgumentException('Enroll parameters not provided'); } } else { - enrollVerbParams = - EnrollParams.fromJson(jsonDecode(verbParams[enrollParams]!)); + enrollVerbParams = EnrollParams.fromJson( + jsonDecode(verbParams[AtConstants.enrollParams]!)); } switch (operation) { case 'request': @@ -116,14 +115,17 @@ class EnrollVerbHandler extends AbstractVerbHandler { throw AtThrottleLimitExceeded( 'Enrollment requests have exceeded the limit within the specified time frame'); } - if (!atConnection.getMetaData().isAuthenticated) { - var otp = enrollParams.otp; - if (otp == null || - (await OtpVerbHandler.cache.get(otp.toString()) == null)) { + + // OTP is sent only in enrollment request which is submitted on + // unauthenticated connection. + if (atConnection.getMetaData().isAuthenticated == false) { + var isValid = await isOTPValid(enrollParams.otp); + if (!isValid) { throw AtEnrollmentException( 'invalid otp. Cannot process enroll request'); } } + var enrollNamespaces = enrollParams.namespaces ?? {}; var newEnrollmentId = Uuid().v4(); var key = @@ -154,8 +156,8 @@ class EnrollVerbHandler extends AbstractVerbHandler { await _storeEncryptionKeys(newEnrollmentId, enrollParams, currentAtSign); // store this apkam as default pkam public key for old clients // The keys with AT_PKAM_PUBLIC_KEY does not sync to client. - await keyStore.put( - AT_PKAM_PUBLIC_KEY, AtData()..data = enrollParams.apkamPublicKey!); + await keyStore.put(AtConstants.atPkamPublicKey, + AtData()..data = enrollParams.apkamPublicKey!); enrollData = AtData()..data = jsonEncode(enrollmentValue.toJson()); } else { enrollmentValue.approval = EnrollApproval(EnrollStatus.pending.name); @@ -171,6 +173,9 @@ class EnrollVerbHandler extends AbstractVerbHandler { } logger.finer('enrollData: $enrollData'); await keyStore.put('$key$currentAtSign', enrollData, skipCommit: true); + // Remove the OTP from keystore to prevent reuse. + await keyStore.remove( + 'private:${enrollParams.otp?.toLowerCase()}${AtSecondaryServerImpl.getInstance().currentAtSign}'); } /// Handles enrollment approve, deny and revoke requests. @@ -224,10 +229,6 @@ class EnrollVerbHandler extends AbstractVerbHandler { // If an enrollment is approved, we need the enrollment to be active // to subsequently revoke the enrollment. Hence reset TTL and // expiredAt on metadata. - /* TODO: Currently TTL is reset on all the enrollments. - However, if the enrollment state is denied or revoked, - unless we wanted to display denied or revoked enrollments in the UI, - we can let the TTL be, so that the enrollment will be deleted subsequently.*/ await _updateEnrollmentValueAndResetTTL( '$enrollmentKey$currentAtSign', enrollDataStoreValue); // when enrollment is approved store the apkamPublicKey of the enrollment @@ -252,13 +253,13 @@ class EnrollVerbHandler extends AbstractVerbHandler { var privKeyJson = {}; privKeyJson['value'] = enrollParams.encryptedDefaultEncryptedPrivateKey; await keyStore.put( - '$newEnrollmentId.$defaultEncryptionPrivateKey.$enrollManageNamespace$atSign', + '$newEnrollmentId.${AtConstants.defaultEncryptionPrivateKey}.$enrollManageNamespace$atSign', AtData()..data = jsonEncode(privKeyJson), skipCommit: true); var selfKeyJson = {}; selfKeyJson['value'] = enrollParams.encryptedDefaultSelfEncryptionKey; await keyStore.put( - '$newEnrollmentId.$defaultSelfEncryptionKey.$enrollManageNamespace$atSign', + '$newEnrollmentId.${AtConstants.defaultSelfEncryptionKey}.$enrollManageNamespace$atSign', AtData()..data = jsonEncode(selfKeyJson), skipCommit: true); } @@ -338,7 +339,7 @@ class EnrollVerbHandler extends AbstractVerbHandler { String key, EnrollParams enrollParams, String atSign) async { try { var notificationValue = {}; - notificationValue[apkamEncryptedSymmetricKey] = + notificationValue[AtConstants.apkamEncryptedSymmetricKey] = enrollParams.encryptedAPKAMSymmetricKey; logger.finer('notificationValue:$notificationValue'); final atNotification = (AtNotificationBuilder() @@ -355,10 +356,10 @@ class EnrollVerbHandler extends AbstractVerbHandler { logger.finer('notification generated: $notificationId'); } on Exception catch (e, trace) { logger.severe( - 'Exception while storing notification key $enrollmentId. Exception $e. Trace $trace'); + 'Exception while storing notification key ${AtConstants.enrollmentId}. Exception $e. Trace $trace'); } on Error catch (e, trace) { logger.severe( - 'Error while storing notification key $enrollmentId. Error $e. Trace $trace'); + 'Error while storing notification key ${AtConstants.enrollmentId}. Error $e. Trace $trace'); } } diff --git a/packages/at_secondary_server/lib/src/verb/handler/otp_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/otp_verb_handler.dart index ac8839cb7..b9b9ae7cd 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/otp_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/otp_verb_handler.dart @@ -1,24 +1,24 @@ import 'dart:collection'; import 'dart:math'; import 'package:at_commons/at_commons.dart'; +import 'package:at_secondary/src/server/at_secondary_impl.dart'; import 'package:at_server_spec/at_server_spec.dart'; +import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; import 'abstract_verb_handler.dart'; import 'package:at_server_spec/at_verb_spec.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; -import 'package:expire_cache/expire_cache.dart'; class OtpVerbHandler extends AbstractVerbHandler { static Otp otpVerb = Otp(); - static final expireDuration = Duration(seconds: 90); - static ExpireCache cache = - ExpireCache(expireDuration: expireDuration); + + @visibleForTesting + int otpExpiryInMills = Duration(minutes: 5).inMilliseconds; OtpVerbHandler(SecondaryKeyStore keyStore) : super(keyStore); @override - bool accept(String command) => - command == 'otp:get' || command.startsWith('otp:validate'); + bool accept(String command) => command == 'otp:get'; @override Verb getVerb() => otpVerb; @@ -29,6 +29,10 @@ class OtpVerbHandler extends AbstractVerbHandler { HashMap verbParams, InboundConnection atConnection) async { final operation = verbParams['operation']; + if (verbParams[AtConstants.ttl] != null && + verbParams[AtConstants.ttl]!.isNotEmpty) { + otpExpiryInMills = int.parse(verbParams[AtConstants.ttl]!); + } switch (operation) { case 'get': if (!atConnection.getMetaData().isAuthenticated) { @@ -40,16 +44,15 @@ class OtpVerbHandler extends AbstractVerbHandler { } // If OTP generated do not have digits, generate again. while (RegExp(r'\d').hasMatch(response.data!) == false); - await cache.set(response.data!, response.data!); - break; - case 'validate': - String? otp = verbParams['otp']; - if (otp != null && (await cache.get(otp)) == otp) { - response.data = 'valid'; - } else { - response.data = 'invalid'; - } + await keyStore.put( + 'private:${response.data}${AtSecondaryServerImpl.getInstance().currentAtSign}', + AtData() + ..data = + '${DateTime.now().toUtc().add(Duration(milliseconds: otpExpiryInMills)).millisecondsSinceEpoch}' + ..metaData = (AtMetaData()..ttl = otpExpiryInMills)); break; + default: + throw InvalidSyntaxException('$operation is not a valid operation'); } } diff --git a/packages/at_secondary_server/pubspec.yaml b/packages/at_secondary_server/pubspec.yaml index 8b96aef6b..982ef2778 100644 --- a/packages/at_secondary_server/pubspec.yaml +++ b/packages/at_secondary_server/pubspec.yaml @@ -19,19 +19,18 @@ dependencies: basic_utils: 5.6.1 ecdsa: 0.0.4 encrypt: 5.0.3 - at_commons: 3.0.56 + at_commons: 3.0.57 at_utils: 3.0.15 at_chops: 1.0.4 at_lookup: 3.0.40 at_server_spec: 3.0.15 at_persistence_spec: 2.0.14 at_persistence_secondary_server: 3.0.58 - expire_cache: ^2.0.1 intl: ^0.18.1 json_annotation: ^4.8.0 version: 3.0.2 meta: 1.10.0 - mutex: 3.0.1 + mutex: 3.1.0 yaml: 3.1.2 logging: 1.2.0 diff --git a/packages/at_secondary_server/test/enroll_verb_test.dart b/packages/at_secondary_server/test/enroll_verb_test.dart index 6e48d943d..236bf5cb7 100644 --- a/packages/at_secondary_server/test/enroll_verb_test.dart +++ b/packages/at_secondary_server/test/enroll_verb_test.dart @@ -41,7 +41,6 @@ void main() { OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb( response, otpVerbParams, inboundConnection); - print('OTP: ${response.data}'); // Enroll request 2 enrollmentRequest = 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"buzz":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; @@ -81,6 +80,31 @@ void main() { expect(enrollmentValue.namespaces.containsKey('__manage'), true); expect(enrollmentValue.namespaces.containsKey('*'), true); }); + + test('A test to verify OTP is deleted once it is used to submit an enrollment',() async { + Response response = Response(); + // OTP Verb + inboundConnection.getMetaData().isAuthenticated = true; + inboundConnection.getMetaData().sessionID = 'dummy_session'; + HashMap otpVerbParams = + getVerbParam(VerbSyntax.otp, 'otp:get'); + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb( + response, otpVerbParams, inboundConnection); + String otp = response.data!; + + String enrollmentRequest = + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"buzz":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; + HashMap enrollmentRequestVerbParams = + getVerbParam(VerbSyntax.enroll, enrollmentRequest); + inboundConnection.getMetaData().isAuthenticated = false; + EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); + await enrollVerbHandler.processVerb( + response, enrollmentRequestVerbParams, inboundConnection); + String enrollmentId = jsonDecode(response.data!)['enrollmentId']; + expect(enrollmentId, isNotNull); + expect(await enrollVerbHandler.isOTPValid(otp), false); + }); tearDown(() async => await verbTestsTearDown()); }); group('A group of tests to verify enroll list operation', () { @@ -316,7 +340,7 @@ void main() { test('A test to verify enrollment request without otp throws exception', () async { String enrollmentRequest = - 'enroll:request:{"appname":"wavi","devicename":"mydevice","namespaces":{"wavi":"r"},"apkampublickey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"apkamPublicKey":"dummy_apkam_public_key"}'; HashMap verbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = false; diff --git a/packages/at_secondary_server/test/otp_verb_test.dart b/packages/at_secondary_server/test/otp_verb_test.dart index 1f7a0276c..a113cf8ce 100644 --- a/packages/at_secondary_server/test/otp_verb_test.dart +++ b/packages/at_secondary_server/test/otp_verb_test.dart @@ -3,7 +3,6 @@ import 'dart:collection'; import 'package:at_commons/at_commons.dart'; import 'package:at_secondary/src/utils/handler_util.dart'; import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart'; -import 'package:expire_cache/expire_cache.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -13,19 +12,20 @@ void main() { setUp(() async { await verbTestsSetUp(); }); - test('A test to verify OTP generated is 6-character length', () { + + test('A test to verify OTP generated is 6-character length', () async { Response response = Response(); HashMap verbParams = getVerbParam(VerbSyntax.otp, 'otp:get'); inboundConnection.getMetaData().isAuthenticated = true; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - otpVerbHandler.processVerb(response, verbParams, inboundConnection); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); expect(response.data, isNotNull); expect(response.data!.length, 6); assert(RegExp('\\d').hasMatch(response.data!)); }); - test('A test to verify same OTP is not returned', () { + test('A test to verify same OTP is not returned', () async { Set otpSet = {}; for (int i = 1; i <= 1000; i++) { Response response = Response(); @@ -33,7 +33,8 @@ void main() { getVerbParam(VerbSyntax.otp, 'otp:get'); inboundConnection.getMetaData().isAuthenticated = true; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - otpVerbHandler.processVerb(response, verbParams, inboundConnection); + await otpVerbHandler.processVerb( + response, verbParams, inboundConnection); expect(response.data, isNotNull); expect(response.data!.length, 6); assert(RegExp('\\d').hasMatch(response.data!)); @@ -42,6 +43,31 @@ void main() { } expect(otpSet.length, 1000); }); + + test('A test to verify otp:get with TTL set is active before TTL is met', + () async { + Response response = Response(); + inboundConnection.getMetaData().isAuthenticated = true; + HashMap verbParams = + getVerbParam(VerbSyntax.otp, 'otp:get:ttl:1000'); + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); + String? otp = response.data; + expect(await otpVerbHandler.isOTPValid(otp), true); + }); + + test('A test to verify otp:get with TTL set expires after the TTL is met', + () async { + Response response = Response(); + inboundConnection.getMetaData().isAuthenticated = true; + HashMap verbParams = + getVerbParam(VerbSyntax.otp, 'otp:get:ttl:1'); + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); + String? otp = response.data; + await Future.delayed(Duration(seconds: 1)); + expect(await otpVerbHandler.isOTPValid(otp), false); + }); tearDown(() async => await verbTestsTearDown()); }); @@ -66,11 +92,11 @@ void main() { tearDown(() async => await verbTestsTearDown()); }); - group('A group of tests related to otp:validate', () { + group('A group of tests related to OTP validity', () { setUp(() async { await verbTestsSetUp(); }); - test('A test to verify otp:validate returns valid when OTP is active', + test('A test to verify isOTPValid method returns valid when OTP is active', () async { Response response = Response(); HashMap verbParams = @@ -78,10 +104,7 @@ void main() { inboundConnection.getMetaData().isAuthenticated = true; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - verbParams = - getVerbParam(VerbSyntax.otp, 'otp:validate:${response.data}'); - await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(response.data, 'valid'); + expect(await otpVerbHandler.isOTPValid(response.data), true); }); test('A test to verify otp:validate returns invalid when OTP is expired', @@ -90,16 +113,20 @@ void main() { HashMap verbParams = getVerbParam(VerbSyntax.otp, 'otp:get'); inboundConnection.getMetaData().isAuthenticated = true; - OtpVerbHandler.cache = - ExpireCache(expireDuration: Duration(microseconds: 1)); OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - print(OtpVerbHandler.cache.expireDuration.inMicroseconds); - await Future.delayed(Duration(microseconds: 2)); + otpVerbHandler.otpExpiryInMills = 1; await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - verbParams = - getVerbParam(VerbSyntax.otp, 'otp:validate:${response.data}'); - await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(response.data, 'invalid'); + String? otp = response.data; + await Future.delayed(Duration(milliseconds: 2)); + expect(await otpVerbHandler.isOTPValid(otp), false); + }); + + test( + 'A test to verify otp:validate return invalid when otp does not exist in keystore', + () async { + String otp = 'ABC123'; + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + expect(await otpVerbHandler.isOTPValid(otp), false); }); tearDown(() async => await verbTestsTearDown()); }); diff --git a/tests/at_functional_test/test/enroll_verb_test.dart b/tests/at_functional_test/test/enroll_verb_test.dart index bf9d6f4b9..537929bb8 100644 --- a/tests/at_functional_test/test/enroll_verb_test.dart +++ b/tests/at_functional_test/test/enroll_verb_test.dart @@ -767,9 +767,6 @@ void main() { socketConnection1!, 'config:set:timeFrameInMills=100\n'); configResponse = await read(); expect(configResponse.trim(), 'data:ok'); - await socket_writer(socketConnection1!, 'otp:get'); - otp = await read(); - otp = otp.replaceAll('data:', '').trim(); }); test( @@ -778,6 +775,9 @@ void main() { SecureSocket unAuthenticatedConnection = await secure_socket_connection(firstAtsignServer, firstAtsignPort); socket_listener(unAuthenticatedConnection); + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); var enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -785,6 +785,10 @@ void main() { jsonDecode((await read()).replaceAll('data:', '')); expect(enrollmentResponse['status'], 'pending'); expect(enrollmentResponse['enrollmentId'], isNotNull); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -801,6 +805,10 @@ void main() { SecureSocket unAuthenticatedConnection = await secure_socket_connection(firstAtsignServer, firstAtsignPort); socket_listener(unAuthenticatedConnection); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); var enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -808,6 +816,10 @@ void main() { jsonDecode((await read()).replaceAll('data:', '')); expect(enrollmentResponse['status'], 'pending'); expect(enrollmentResponse['enrollmentId'], isNotNull); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -828,6 +840,10 @@ void main() { SecureSocket unAuthenticatedConnection = await secure_socket_connection(firstAtsignServer, firstAtsignPort); socket_listener(unAuthenticatedConnection); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); var enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -835,6 +851,10 @@ void main() { jsonDecode((await read()).replaceAll('data:', '')); expect(enrollmentResponse['status'], 'pending'); expect(enrollmentResponse['enrollmentId'], isNotNull); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(unAuthenticatedConnection, enrollRequest); @@ -847,6 +867,10 @@ void main() { SecureSocket secondUnAuthenticatedConnection2 = await secure_socket_connection(firstAtsignServer, firstAtsignPort); socket_listener(secondUnAuthenticatedConnection2); + + await socket_writer(socketConnection1!, 'otp:get'); + otp = await read(); + otp = otp.replaceAll('data:', '').trim(); enrollRequest = 'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n'; await socket_writer(secondUnAuthenticatedConnection2, enrollRequest);