Skip to content

Commit

Permalink
Merge pull request #1533 from atsign-foundation/change_otp_impl
Browse files Browse the repository at this point in the history
fix: Change OTP generation and rename TOTP to OTP
  • Loading branch information
sitaram-kalluri authored Aug 31, 2023
2 parents 80b5963 + b9b2b39 commit 57d4daf
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 182 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
part 'enroll_datastore_value.g.dart';

@JsonSerializable()
part 'enroll_datastore_value.g.dart';

/// Represents attributes for APKAM enrollment data
@JsonSerializable()
class EnrollDataStoreValue {
late String sessionId;
late String appName;
late String deviceName;

// map for representing namespace access. key will be the namespace, value will be the access
// e.g {'wavi':'r', 'buzz':'rw'}
Map<String, String> namespaces = {};
late String apkamPublicKey;
EnrollRequestType? requestType;
EnrollApproval? approval;

EnrollDataStoreValue(
this.sessionId, this.appName, this.deviceName, this.apkamPublicKey);

Expand All @@ -25,8 +27,11 @@ class EnrollDataStoreValue {

class EnrollApproval {
String state;

EnrollApproval(this.state);

EnrollApproval.fromJson(Map<String, dynamic> json) : state = json['state'];

Map<String, dynamic> toJson() => {
'state': state,
};
Expand All @@ -37,6 +42,4 @@ class EnrollApproval {
}
}

enum EnrollStatus { pending, approved, denied, revoked }

enum EnrollRequestType { newEnrollment, changeEnrollment }
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:at_secondary/src/server/at_secondary_impl.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/totp_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:at_server_spec/at_verb_spec.dart';
import 'package:uuid/uuid.dart';
Expand Down Expand Up @@ -100,11 +100,11 @@ class EnrollVerbHandler extends AbstractVerbHandler {
Map<dynamic, dynamic> responseJson,
InboundConnection atConnection) async {
if (!atConnection.getMetaData().isAuthenticated) {
var totp = enrollParams.totp;
if (totp == null ||
(await TotpVerbHandler.cache.get(totp.toString()) == null)) {
var otp = enrollParams.otp;
if (otp == null ||
(await OtpVerbHandler.cache.get(otp.toString()) == null)) {
throw AtEnrollmentException(
'invalid totp. Cannot process enroll request');
'invalid otp. Cannot process enroll request');
}
}
var enrollNamespaces = enrollParams.namespaces ?? {};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'dart:collection';
import 'dart:math';
import 'package:at_commons/at_commons.dart';
import 'package:at_server_spec/at_server_spec.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<String, String> cache =
ExpireCache<String, String>(expireDuration: expireDuration);

OtpVerbHandler(SecondaryKeyStore keyStore) : super(keyStore);

@override
bool accept(String command) =>
command == 'otp:get' || command.startsWith('otp:validate');

@override
Verb getVerb() => otpVerb;

@override
Future<void> processVerb(
Response response,
HashMap<String, String?> verbParams,
InboundConnection atConnection) async {
final operation = verbParams['operation'];
switch (operation) {
case 'get':
if (!atConnection.getMetaData().isAuthenticated) {
throw UnAuthenticatedException(
'otp:get requires authenticated connection');
}
do {
response.data = _generateOTP();
}
// 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';
}
break;
}
}

/// This function generates a UUID and converts it into a 6-character alpha-numeric string.
///
/// The process involves converting the UUID to a hashcode, then transforming the hashcode
/// into its Hexatridecimal representation to obtain the desired alpha-numeric characters.
///
/// Additionally, if the resulting OTP contains "0" or "O", they are replaced with different
/// number or alphabet, respectively. If the length of the OTP is less than 6, "padRight"
/// is utilized to extend and match the length.
String _generateOTP() {
var uuid = Uuid().v4();
Random random = Random();
var otp = uuid.hashCode.toRadixString(36).toUpperCase();
// If otp contains "0"(Zero) or "O" (alphabet) replace with a different number
// or alphabet respectively.
if (otp.contains('0') || otp.contains('O')) {
for (int i = 0; i < otp.length; i++) {
if (otp[i] == '0') {
otp = otp.replaceFirst('0', (random.nextInt(8) + 1).toString());
} else if (otp[i] == 'O') {
otp = otp.replaceFirst('O', _generateRandomAlphabet());
}
}
}
if (otp.length < 6) {
otp = otp.padRight(6, _generateRandomAlphabet());
}
return otp;
}

String _generateRandomAlphabet() {
int minAscii = 'A'.codeUnitAt(0); // ASCII value of 'A'
int maxAscii = 'Z'.codeUnitAt(0); // ASCII value of 'Z'
int randomAscii;
do {
randomAscii = minAscii + Random().nextInt((maxAscii - minAscii) + 1);
// 79 is the ASCII value of "O". If randamAscii is 79, generate again.
} while (randomAscii == 79);
return String.fromCharCode(randomAscii);
}

int bytesToInt(List<int> bytes) {
int result = 0;
for (final b in bytes) {
result = result * 256 + b;
}
return result;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import 'package:at_secondary/src/verb/handler/scan_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/stats_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/stream_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/sync_progressive_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/totp_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/update_meta_verb_handler.dart';
import 'package:at_secondary/src/verb/handler/update_verb_handler.dart';
import 'package:at_server_spec/at_verb_spec.dart';
Expand Down Expand Up @@ -104,7 +104,7 @@ class DefaultVerbHandlerManager implements VerbHandlerManager {
_verbHandlers.add(NotifyRemoveVerbHandler(keyStore));
_verbHandlers.add(NotifyFetchVerbHandler(keyStore));
_verbHandlers.add(EnrollVerbHandler(keyStore));
_verbHandlers.add(TotpVerbHandler(keyStore));
_verbHandlers.add(OtpVerbHandler(keyStore));
_verbHandlers.add(KeysVerbHandler(keyStore));
return _verbHandlers;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/at_secondary_server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ dependencies:
collection: 1.18.0
basic_utils: 5.6.1
ecdsa: 0.0.4
at_commons: 3.0.53
at_commons: 3.0.54
at_utils: 3.0.15
at_chops: 1.0.4
at_lookup: 3.0.39
at_server_spec: 3.0.13
at_server_spec: 3.0.14
at_persistence_spec: 2.0.14
at_persistence_secondary_server: 3.0.57
expire_cache: ^2.0.1
Expand Down
Loading

0 comments on commit 57d4daf

Please sign in to comment.