code.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import 'package:ente_auth/utils/totp_util.dart';
  2. class Code {
  3. static const defaultDigits = 6;
  4. static const defaultPeriod = 30;
  5. int? generatedID;
  6. final String account;
  7. final String issuer;
  8. final int digits;
  9. final int period;
  10. final String secret;
  11. final Algorithm algorithm;
  12. final Type type;
  13. final String rawData;
  14. bool? hasSynced;
  15. Code(
  16. this.account,
  17. this.issuer,
  18. this.digits,
  19. this.period,
  20. this.secret,
  21. this.algorithm,
  22. this.type,
  23. this.rawData, {
  24. this.generatedID,
  25. });
  26. Code copyWith({
  27. String? account,
  28. String? issuer,
  29. int? digits,
  30. int? period,
  31. String? secret,
  32. Algorithm? algorithm,
  33. Type? type,
  34. }) {
  35. final String updateAccount = account ?? this.account;
  36. final String updateIssuer = issuer ?? this.issuer;
  37. final int updatedDigits = digits ?? this.digits;
  38. final int updatePeriod = period ?? this.period;
  39. final String updatedSecret = secret ?? this.secret;
  40. final Algorithm updatedAlgo = algorithm ?? this.algorithm;
  41. final Type updatedType = type ?? this.type;
  42. return Code(
  43. updateAccount,
  44. updateIssuer,
  45. updatedDigits,
  46. updatePeriod,
  47. updatedSecret,
  48. updatedAlgo,
  49. updatedType,
  50. "otpauth://${updatedType.name}/" +
  51. updateIssuer +
  52. ":" +
  53. updateAccount +
  54. "?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
  55. updateIssuer +
  56. "&period=$updatePeriod&secret=" +
  57. updatedSecret,
  58. generatedID: generatedID,
  59. );
  60. }
  61. static Code fromAccountAndSecret(
  62. String account,
  63. String issuer,
  64. String secret,
  65. ) {
  66. return Code(
  67. account,
  68. issuer,
  69. defaultDigits,
  70. defaultPeriod,
  71. secret,
  72. Algorithm.sha1,
  73. Type.totp,
  74. "otpauth://totp/" +
  75. issuer +
  76. ":" +
  77. account +
  78. "?algorithm=SHA1&digits=6&issuer=" +
  79. issuer +
  80. "&period=30&secret=" +
  81. secret,
  82. );
  83. }
  84. static Code fromRawData(String rawData) {
  85. Uri uri = Uri.parse(rawData);
  86. try {
  87. return Code(
  88. _getAccount(uri),
  89. _getIssuer(uri),
  90. _getDigits(uri),
  91. _getPeriod(uri),
  92. getSanitizedSecret(uri.queryParameters['secret']!),
  93. _getAlgorithm(uri),
  94. _getType(uri),
  95. rawData,
  96. );
  97. } catch(e) {
  98. // if account name contains # without encoding,
  99. // rest of the url are treated as url fragment
  100. if(rawData.contains("#")) {
  101. return Code.fromRawData(rawData.replaceAll("#", '%23'));
  102. } else {
  103. rethrow;
  104. }
  105. }
  106. }
  107. static String _getAccount(Uri uri) {
  108. try {
  109. String path = Uri.decodeComponent(uri.path);
  110. if (path.startsWith("/")) {
  111. path = path.substring(1, path.length);
  112. }
  113. // Parse account name from documented auth URI
  114. // otpauth://totp/ACCOUNT?secret=SUPERSECRET&issuer=SERVICE
  115. if (uri.queryParameters.containsKey("issuer") && !path.contains(":")) {
  116. return path;
  117. }
  118. return path.split(':')[1];
  119. } catch (e) {
  120. return "";
  121. }
  122. }
  123. static String _getIssuer(Uri uri) {
  124. try {
  125. if (uri.queryParameters.containsKey("issuer")) {
  126. String issuerName = uri.queryParameters['issuer']!;
  127. // Handle issuer name with period
  128. // See https://github.com/ente-io/auth/pull/77
  129. if (issuerName.contains("period=")) {
  130. return issuerName.substring(0, issuerName.indexOf("period="));
  131. }
  132. return issuerName;
  133. }
  134. final String path = Uri.decodeComponent(uri.path);
  135. return path.split(':')[0].substring(1);
  136. } catch (e) {
  137. return "";
  138. }
  139. }
  140. static int _getDigits(Uri uri) {
  141. try {
  142. return int.parse(uri.queryParameters['digits']!);
  143. } catch (e) {
  144. return defaultDigits;
  145. }
  146. }
  147. static int _getPeriod(Uri uri) {
  148. try {
  149. return int.parse(uri.queryParameters['period']!);
  150. } catch (e) {
  151. return defaultPeriod;
  152. }
  153. }
  154. static Algorithm _getAlgorithm(Uri uri) {
  155. try {
  156. final algorithm =
  157. uri.queryParameters['algorithm'].toString().toLowerCase();
  158. if (algorithm == "sha256") {
  159. return Algorithm.sha256;
  160. } else if (algorithm == "sha512") {
  161. return Algorithm.sha512;
  162. }
  163. } catch (e) {
  164. // nothing
  165. }
  166. return Algorithm.sha1;
  167. }
  168. static Type _getType(Uri uri) {
  169. if (uri.host == "totp") {
  170. return Type.totp;
  171. } else if (uri.host == "hotp") {
  172. return Type.hotp;
  173. }
  174. throw UnsupportedError("Unsupported format with host ${uri.host}");
  175. }
  176. @override
  177. bool operator ==(Object other) {
  178. if (identical(this, other)) return true;
  179. return other is Code &&
  180. other.account == account &&
  181. other.issuer == issuer &&
  182. other.digits == digits &&
  183. other.period == period &&
  184. other.secret == secret &&
  185. other.type == type &&
  186. other.rawData == rawData;
  187. }
  188. @override
  189. int get hashCode {
  190. return account.hashCode ^
  191. issuer.hashCode ^
  192. digits.hashCode ^
  193. period.hashCode ^
  194. secret.hashCode ^
  195. type.hashCode ^
  196. rawData.hashCode;
  197. }
  198. }
  199. enum Type {
  200. totp,
  201. hotp,
  202. }
  203. enum Algorithm {
  204. sha1,
  205. sha256,
  206. sha512,
  207. }