From 8e85f33f4217d9f3780e40518fadae85ca15b55b Mon Sep 17 00:00:00 2001 From: gkc Date: Fri, 24 May 2024 14:57:49 +0100 Subject: [PATCH 1/3] feat: allow a ttl to be setup for a semi-permanent passcode (spp) - feat: have AbstractVerbHandler.isOTPValid do an 'isActiveKey' check after fetching the SPP - refactor: add a `passcodeKey` function to remove some code duplication - fix: fix the UnauthenticatedException message in OtpVerbHandler to be 'otp: [...]' instead of 'otp:get [...]' - refactor: change some variable names across 'put' and 'get' blocks in OtpVerbHandler for readability - refactor: add isSpp parameter to savePasscode and use it in OtpVerbHandler's 'put' block --- .../verb/handler/abstract_verb_handler.dart | 24 ++++++----- .../src/verb/handler/otp_verb_handler.dart | 37 +++++++++------- .../test/monitor_verb_test.dart | 9 ++-- .../test/otp_verb_test.dart | 42 ++++++++++++++++++- 4 files changed, 81 insertions(+), 31 deletions(-) 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 12b943118..e687c949e 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 @@ -274,39 +274,43 @@ abstract class AbstractVerbHandler implements VerbHandler { if (otp == null) { return false; } - // Check if user has configured SPP(Semi-Permanent Pass-code). + // 1. Check if user has configured an SPP(Semi-Permanent Pass-code). // If SPP key is available, check if the otp sent is a valid pass code. // If yes, return true, else check it is a valid OTP. - String sppKey = - 'private:spp.${OtpVerbHandler.otpNamespace}${AtSecondaryServerImpl.getInstance().currentAtSign}'; - if (!keyStore.isKeyExists(sppKey)) { + String passcodeKey = OtpVerbHandler.passcodeKey(otp, isSpp: true); + if (!keyStore.isKeyExists(passcodeKey)) { // if new SPPKey does not exist in keystore, check for SPP data against legacy SPP key // New SPP key has __otp namespace, legacy key does NOT have any namespace - sppKey = + passcodeKey = 'private:spp${AtSecondaryServerImpl.getInstance().currentAtSign}'; } try { - AtData? sppAtData = await keyStore.get(sppKey); + AtData? sppAtData = await keyStore.get(passcodeKey); // SPP has a special key so we have to check the value that was stored // (which is the actual SPP) // By comparison, OTPs are stored with the key being ${OTP}.__otp@alice // i.e. the OTP is part of the key, and the stored data is irrelevant if (sppAtData?.data?.toLowerCase() == otp.toLowerCase()) { - return true; + if (SecondaryUtil.isActiveKey(sppAtData)) { + return true; + } else { + logger.finest( + 'SPP found in KeyStore but has expired. Validating as OTP'); + } } } on KeyNotFoundException { logger.finest('No SPP found in KeyStore. Validating as OTP'); } - // If not a valid SPP, then check against OTP keys - String otpKey = - 'private:${otp.toLowerCase()}.${OtpVerbHandler.otpNamespace}${AtSecondaryServerImpl.getInstance().currentAtSign}'; + // 2. If not a valid SPP, then check against OTP keys + String otpKey = OtpVerbHandler.passcodeKey(otp, isSpp: false); if (!keyStore.isKeyExists(otpKey)) { // if new OTPKey does not exist in keystore, check for OTP data against legacy OTPKey // New OTP key has __otp namespace, legacy key does not have namespace otpKey = 'private:${otp.toLowerCase()}${AtSecondaryServerImpl.getInstance().currentAtSign}'; } + AtData? otpAtData; try { otpAtData ??= await keyStore.get(otpKey); 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 b73690d69..68f6e35bc 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 @@ -36,17 +36,15 @@ class OtpVerbHandler extends AbstractVerbHandler { InboundConnection atConnection) async { final operation = verbParams['operation']; if (!atConnection.metaData.isAuthenticated) { - throw UnAuthenticatedException( - 'otp:get requires authenticated connection'); + throw UnAuthenticatedException('otp: requires authenticated connection'); } switch (operation) { case 'get': String otp = generateOTP(); // Extract the ttl from the verb parameters if supplied, or use the default value. - int otpExpiryInMillis = - int.tryParse(verbParams[AtConstants.ttl] ?? '') ?? - defaultOtpExpiry.inMilliseconds; - await saveOTP(otp, otpExpiryInMillis); + int otpTtl = int.tryParse(verbParams[AtConstants.ttl] ?? '') ?? + defaultOtpExpiry.inMilliseconds; + await savePasscode(otp, ttl: otpTtl, isSpp: false); response.data = otp; break; case 'put': @@ -57,10 +55,12 @@ class OtpVerbHandler extends AbstractVerbHandler { throw InvalidRequestException( 'Client not allowed to not store semi permanent pass code'); } - String? otp = verbParams['otp']; - await keyStore.put( - 'private:spp.$otpNamespace${AtSecondaryServerImpl.getInstance().currentAtSign}', - AtData()..data = otp); + int sppTtl = int.tryParse(verbParams[AtConstants.ttl] ?? '') ?? -1; + String? spp = verbParams['otp']; + if (spp == null) { + throw InvalidRequestException('otp:put requires a passcode'); + } + await savePasscode(spp, ttl: sppTtl, isSpp: true); response.data = 'ok'; break; default: @@ -100,12 +100,21 @@ class OtpVerbHandler extends AbstractVerbHandler { return otp; } - Future saveOTP(String otp, int otpExpiryInMillis) async { + static String passcodeKey(String passcode, {required bool isSpp}) { + return isSpp + ? 'private:spp.$otpNamespace' + '${AtSecondaryServerImpl.getInstance().currentAtSign}' + : 'private:${passcode.toLowerCase()}.$otpNamespace' + '${AtSecondaryServerImpl.getInstance().currentAtSign}'; + } + + Future savePasscode(String passcode, + {required int ttl, required bool isSpp}) async { await keyStore.put( - 'private:$otp.$otpNamespace${AtSecondaryServerImpl.getInstance().currentAtSign}', + passcodeKey(passcode, isSpp: isSpp), AtData() - ..data = otp - ..metaData = (AtMetaData()..ttl = otpExpiryInMillis)); + ..data = passcode + ..metaData = (AtMetaData()..ttl = ttl)); } String _generateRandomAlphabet() { diff --git a/packages/at_secondary_server/test/monitor_verb_test.dart b/packages/at_secondary_server/test/monitor_verb_test.dart index a1c66b5d4..73e12fd63 100644 --- a/packages/at_secondary_server/test/monitor_verb_test.dart +++ b/packages/at_secondary_server/test/monitor_verb_test.dart @@ -11,7 +11,6 @@ import 'package:at_secondary/src/verb/handler/enroll_verb_handler.dart'; import 'package:at_secondary/src/verb/handler/monitor_verb_handler.dart'; import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart'; import 'package:at_server_spec/at_server_spec.dart'; -import 'package:test/expect.dart'; import 'package:test/test.dart'; import 'package:uuid/uuid.dart'; @@ -351,7 +350,7 @@ void main() { {required bool autoApprove}) async { OtpVerbHandler otpVH = OtpVerbHandler(secondaryKeyStore); String otp = otpVH.generateOTP(); - await otpVH.saveOTP(otp, 5000); + await otpVH.savePasscode(otp, ttl: 5000, isSpp: false); EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); @@ -361,7 +360,7 @@ void main() { ',"deviceName":"$deviceName"' ',"namespaces":${jsonEncode(namespaces)}' ',"apkamPublicKey":"dummy_apkam_public_key"' - ',"encryptedAPKAMSymmetricKey":"dummy_encrypted_apkam_symm_key"' + ',"encryptedAPKAMSymmetricKey":"dummy_encrypted_apkam_symmetric_key"' '}'; HashMap enrollmentRequestVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); @@ -480,9 +479,9 @@ void main() { final valueJson = jsonDecode(notificationJson['value']); //TODO remove encryptedApkamSymmetricKey in the future expect(valueJson['encryptedApkamSymmetricKey'], - 'dummy_encrypted_apkam_symm_key'); + 'dummy_encrypted_apkam_symmetric_key'); expect(valueJson['encryptedAPKAMSymmetricKey'], - 'dummy_encrypted_apkam_symm_key'); + 'dummy_encrypted_apkam_symmetric_key'); expect(valueJson['appName'], 'mvt_app_2'); expect(valueJson['deviceName'], 'mvt_dev_2'); expect(valueJson['namespace'], equals({'app_2_namespace': 'rw'})); diff --git a/packages/at_secondary_server/test/otp_verb_test.dart b/packages/at_secondary_server/test/otp_verb_test.dart index fdbb85c4e..c22b18c08 100644 --- a/packages/at_secondary_server/test/otp_verb_test.dart +++ b/packages/at_secondary_server/test/otp_verb_test.dart @@ -113,7 +113,7 @@ void main() { await verbTestsSetUp(); }); test( - 'A test to verify UnAuthorizedException is thrown when opt verb is executed on an unauthenticated conn', + 'A test to verify UnAuthorizedException is thrown when opt:get is executed on an unauthenticated conn', () { Response response = Response(); HashMap verbParams = @@ -124,8 +124,9 @@ void main() { response, verbParams, inboundConnection), throwsA(predicate((e) => e is UnAuthenticatedException && - e.message == 'otp:get requires authenticated connection'))); + e.message == 'otp: requires authenticated connection'))); }); + tearDown(() async => await verbTestsTearDown()); }); @@ -227,6 +228,7 @@ void main() { await secondaryKeyStore.put( enrollmentKey, AtData()..data = jsonEncode(enrollDataStoreValue)); }); + test('A test to set a pass code and verify isOTPValid returns true', () async { String passcode = 'abc123'; @@ -244,6 +246,42 @@ void main() { expect(await otpVerbHandler.isOTPValid(passcode), true); }); + test('set spp with a ttl, check isOTPValid before ttl expires', () async { + String passcode = 'SppWithTtl50'; + Response response = Response(); + HashMap verbParams = + getVerbParam(VerbSyntax.otp, 'otp:put:$passcode:ttl:50'); + inboundConnection.metaData.isAuthenticated = true; + (inboundConnection.metaData as InboundConnectionMetadata).enrollmentId = + enrollmentId; + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); + expect(await otpVerbHandler.isOTPValid(passcode), true); + // Adding expect again to ensure the Semi-permanent passcodes are not deleted + // after one time use. + expect(await otpVerbHandler.isOTPValid(passcode), true); + }); + + test('set spp with a ttl, check isOTPValid before and after ttl expires', + () async { + String passcode = 'SppWithTtl50'; + Response response = Response(); + HashMap verbParams = + getVerbParam(VerbSyntax.otp, 'otp:put:$passcode:ttl:50'); + inboundConnection.metaData.isAuthenticated = true; + (inboundConnection.metaData as InboundConnectionMetadata).enrollmentId = + enrollmentId; + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); + expect(await otpVerbHandler.isOTPValid(passcode), true); + // Adding expect again to ensure the Semi-permanent passcodes are not deleted + // after one time use. + expect(await otpVerbHandler.isOTPValid(passcode), true); + + await Future.delayed(Duration(milliseconds: 51)); + expect(await otpVerbHandler.isOTPValid(passcode), false); + }); + test('A test to verify pass code can be updated', () async { String passcode = 'abc123'; Response response = Response(); From 0fdd619c9ba59df679ceb6775167f7414c9f352f Mon Sep 17 00:00:00 2001 From: gkc Date: Fri, 24 May 2024 15:06:56 +0100 Subject: [PATCH 2/3] test: add a unit test to verify that the ttl is set to -1 (do not expire) if a ttl is not supplied to otp:put refactor: rename isOTPValid to isPasscodeValid --- .../verb/handler/abstract_verb_handler.dart | 12 ++-- .../src/verb/handler/enroll_verb_handler.dart | 2 +- .../test/abstract_verb_handler_test.dart | 2 +- .../test/enroll_verb_test.dart | 2 +- .../test/otp_verb_test.dart | 55 +++++++++++++------ 5 files changed, 46 insertions(+), 27 deletions(-) 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 e687c949e..33b71e6cb 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 @@ -270,14 +270,14 @@ abstract class AbstractVerbHandler implements VerbHandler { /// /// Additionally, this function removes the OTP from the keystore to prevent /// its reuse. - Future isOTPValid(String? otp) async { - if (otp == null) { + Future isPasscodeValid(String? passcode) async { + if (passcode == null) { return false; } // 1. Check if user has configured an SPP(Semi-Permanent Pass-code). // If SPP key is available, check if the otp sent is a valid pass code. // If yes, return true, else check it is a valid OTP. - String passcodeKey = OtpVerbHandler.passcodeKey(otp, isSpp: true); + String passcodeKey = OtpVerbHandler.passcodeKey(passcode, isSpp: true); if (!keyStore.isKeyExists(passcodeKey)) { // if new SPPKey does not exist in keystore, check for SPP data against legacy SPP key // New SPP key has __otp namespace, legacy key does NOT have any namespace @@ -290,7 +290,7 @@ abstract class AbstractVerbHandler implements VerbHandler { // (which is the actual SPP) // By comparison, OTPs are stored with the key being ${OTP}.__otp@alice // i.e. the OTP is part of the key, and the stored data is irrelevant - if (sppAtData?.data?.toLowerCase() == otp.toLowerCase()) { + if (sppAtData?.data?.toLowerCase() == passcode.toLowerCase()) { if (SecondaryUtil.isActiveKey(sppAtData)) { return true; } else { @@ -303,12 +303,12 @@ abstract class AbstractVerbHandler implements VerbHandler { } // 2. If not a valid SPP, then check against OTP keys - String otpKey = OtpVerbHandler.passcodeKey(otp, isSpp: false); + String otpKey = OtpVerbHandler.passcodeKey(passcode, isSpp: false); if (!keyStore.isKeyExists(otpKey)) { // if new OTPKey does not exist in keystore, check for OTP data against legacy OTPKey // New OTP key has __otp namespace, legacy key does not have namespace otpKey = - 'private:${otp.toLowerCase()}${AtSecondaryServerImpl.getInstance().currentAtSign}'; + 'private:${passcode.toLowerCase()}${AtSecondaryServerImpl.getInstance().currentAtSign}'; } AtData? 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 1a13fc9d8..2a7542a18 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 @@ -191,7 +191,7 @@ class EnrollVerbHandler extends AbstractVerbHandler { // OTP is sent only in enrollment request which is submitted on // unauthenticated connection. if (atConnection.metaData.isAuthenticated == false) { - var isValid = await isOTPValid(enrollParams.otp); + var isValid = await isPasscodeValid(enrollParams.otp); if (!isValid) { _lastInvalidOtpReceivedInMills = DateTime.now().toUtc().millisecondsSinceEpoch; diff --git a/packages/at_secondary_server/test/abstract_verb_handler_test.dart b/packages/at_secondary_server/test/abstract_verb_handler_test.dart index d3d1110c5..9a3bc0c9f 100644 --- a/packages/at_secondary_server/test/abstract_verb_handler_test.dart +++ b/packages/at_secondary_server/test/abstract_verb_handler_test.dart @@ -97,7 +97,7 @@ class TestUpdateVerbHandler extends AbstractVerbHandler { } @override - Future isOTPValid(String? otp) { + Future isPasscodeValid(String? otp) { // TODO: implement isOTPValid throw UnimplementedError(); } diff --git a/packages/at_secondary_server/test/enroll_verb_test.dart b/packages/at_secondary_server/test/enroll_verb_test.dart index 458ab7a46..cfb10c0ba 100644 --- a/packages/at_secondary_server/test/enroll_verb_test.dart +++ b/packages/at_secondary_server/test/enroll_verb_test.dart @@ -108,7 +108,7 @@ void main() { response, enrollmentRequestVerbParams, inboundConnection); String enrollmentId = jsonDecode(response.data!)['enrollmentId']; expect(enrollmentId, isNotNull); - expect(await enrollVerbHandler.isOTPValid(otp), false); + expect(await enrollVerbHandler.isPasscodeValid(otp), false); }); tearDown(() async => await verbTestsTearDown()); }); diff --git a/packages/at_secondary_server/test/otp_verb_test.dart b/packages/at_secondary_server/test/otp_verb_test.dart index c22b18c08..ac2903dbe 100644 --- a/packages/at_secondary_server/test/otp_verb_test.dart +++ b/packages/at_secondary_server/test/otp_verb_test.dart @@ -90,7 +90,7 @@ void main() { OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); String? otp = response.data; - expect(await otpVerbHandler.isOTPValid(otp), true); + expect(await otpVerbHandler.isPasscodeValid(otp), true); }); test('A test to verify otp:get with TTL set expires after the TTL is met', @@ -103,7 +103,7 @@ void main() { await otpVerbHandler.processVerb(response, verbParams, inboundConnection); String? otp = response.data; await Future.delayed(Duration(seconds: 1)); - expect(await otpVerbHandler.isOTPValid(otp), false); + expect(await otpVerbHandler.isPasscodeValid(otp), false); }); tearDown(() async => await verbTestsTearDown()); }); @@ -142,7 +142,7 @@ void main() { inboundConnection.metaData.isAuthenticated = true; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(response.data), true); + expect(await otpVerbHandler.isPasscodeValid(response.data), true); }); test('A test to verify otp:validate returns invalid when OTP is expired', @@ -155,7 +155,7 @@ void main() { await otpVerbHandler.processVerb(response, verbParams, inboundConnection); String? otp = response.data; await Future.delayed(Duration(milliseconds: 2)); - expect(await otpVerbHandler.isOTPValid(otp), false); + expect(await otpVerbHandler.isPasscodeValid(otp), false); }); test('A test to verify default otp expiry not overwritten', () async { @@ -183,7 +183,7 @@ void main() { () async { String otp = 'ABC123'; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - expect(await otpVerbHandler.isOTPValid(otp), false); + expect(await otpVerbHandler.isPasscodeValid(otp), false); }); test('A test to verify otp is removed from the keystore after use', @@ -195,8 +195,8 @@ void main() { OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); String? otp = response.data; - expect(await otpVerbHandler.isOTPValid(otp), true); - expect(await otpVerbHandler.isOTPValid(otp), false); + expect(await otpVerbHandler.isPasscodeValid(otp), true); + expect(await otpVerbHandler.isPasscodeValid(otp), false); }); test('validate backwards compatability with legacy otp key', () async { @@ -210,7 +210,7 @@ void main() { await secondaryKeyStore.put(otpLegacyKey, value); OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - expect(await otpVerbHandler.isOTPValid(testOtp), true); + expect(await otpVerbHandler.isPasscodeValid(testOtp), true); }); tearDown(() async => await verbTestsTearDown()); }); @@ -240,10 +240,10 @@ void main() { enrollmentId; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); // Adding expect again to ensure the Semi-permanent passcodes are not deleted // after one time use. - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); }); test('set spp with a ttl, check isOTPValid before ttl expires', () async { @@ -256,10 +256,11 @@ void main() { enrollmentId; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); // Adding expect again to ensure the Semi-permanent passcodes are not deleted // after one time use. - expect(await otpVerbHandler.isOTPValid(passcode), true); + await Future.delayed(Duration(milliseconds: 10)); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); }); test('set spp with a ttl, check isOTPValid before and after ttl expires', @@ -273,13 +274,31 @@ void main() { enrollmentId; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); // Adding expect again to ensure the Semi-permanent passcodes are not deleted // after one time use. - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); await Future.delayed(Duration(milliseconds: 51)); - expect(await otpVerbHandler.isOTPValid(passcode), false); + expect(await otpVerbHandler.isPasscodeValid(passcode), false); + }); + + test('set spp without a ttl, verify its ttl has been set to -1', () async { + String passcode = 'SppWithoutTtl'; + Response response = Response(); + HashMap verbParams = + getVerbParam(VerbSyntax.otp, 'otp:put:$passcode'); + inboundConnection.metaData.isAuthenticated = true; + (inboundConnection.metaData as InboundConnectionMetadata).enrollmentId = + enrollmentId; + OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); + await otpVerbHandler.processVerb(response, verbParams, inboundConnection); + await Future.delayed(Duration(milliseconds: 2)); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); + + String sppKey = OtpVerbHandler.passcodeKey(passcode, isSpp: true); + var atData = await secondaryKeyStore.get(sppKey); + expect(atData?.metaData?.ttl, -1); }); test('A test to verify pass code can be updated', () async { @@ -292,14 +311,14 @@ void main() { enrollmentId; OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); // Update the pass-code passcode = 'xyz987'; response = Response(); verbParams = getVerbParam(VerbSyntax.otp, 'otp:put:$passcode'); otpVerbHandler = OtpVerbHandler(secondaryKeyStore); await otpVerbHandler.processVerb(response, verbParams, inboundConnection); - expect(await otpVerbHandler.isOTPValid(passcode), true); + expect(await otpVerbHandler.isPasscodeValid(passcode), true); }); test('validate backwards compatability with legacy ssp key', () async { @@ -310,7 +329,7 @@ void main() { await secondaryKeyStore.put(otpLegacyKey, value); OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); - expect(await otpVerbHandler.isOTPValid(testOtp), true); + expect(await otpVerbHandler.isPasscodeValid(testOtp), true); }); tearDown(() async => await verbTestsTearDown()); }); From b72e4adbcb92acf1dd7278bb5d6a821c16efb8d4 Mon Sep 17 00:00:00 2001 From: gkc Date: Fri, 24 May 2024 15:13:43 +0100 Subject: [PATCH 3/3] docs: update CHANGELOG --- packages/at_secondary_server/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/at_secondary_server/CHANGELOG.md b/packages/at_secondary_server/CHANGELOG.md index c94d6084b..5a9baccbd 100644 --- a/packages/at_secondary_server/CHANGELOG.md +++ b/packages/at_secondary_server/CHANGELOG.md @@ -1,5 +1,6 @@ ## 3.0.47 - feat: Introduced a dedicated namespace for storing OTPs +- feat: allow a ttl to be set for a semi-permanent passcode (spp) ## 3.0.46 - fix: Default OTP expiry value remains unchanged for the subsequent "otp:" requests