Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HOTP algorithm #106

Merged
merged 11 commits into from
Jul 15, 2022
15 changes: 14 additions & 1 deletion core/shared/src/main/scala/bobcats/Hotp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ sealed trait Hotp[F[_]] extends HotpPlatform[F] {
def generate(
key: SecretKey[HmacAlgorithm.SHA1.type],
movingFactor: Long,
digits: Int = 6
digits: Int
): F[Int]

def generate(
key: SecretKey[HmacAlgorithm.SHA1.type],
movingFactor: Long
): F[Int]
}

Expand All @@ -38,11 +43,19 @@ object Hotp extends HotpCompanionPlatform {

implicit def forSync[F[_]](implicit F: Sync[F], H: Hmac[F]): Hotp[F] =
new UnsealedHotp[F] {
override def generate(
key: SecretKey[HmacAlgorithm.SHA1.type],
movingFactor: Long): F[Int] =
generate(key, movingFactor, digits = 6)
DavidGregory084 marked this conversation as resolved.
Show resolved Hide resolved

override def generate(
key: SecretKey[HmacAlgorithm.SHA1.type],
movingFactor: Long,
digits: Int
): F[Int] = {
require(digits >= 6, s"digits must be at least 6, was $digits")
require(digits < 10, s"digits must be less than 10, was $digits")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wrap this in F.catchNonFatal so that errors suspend properly into F.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, why do we need Sync here? Would Functor be enough? In which case you can leave the require as-is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a design question here whether we need an Hotp capability trait actually since it's not hiding bigger constraints. We could implement these as methods on an object, that take Functor and Hmac constraints. I'd need to look to other Typelevel libs for inspiration on the right way to do this :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there possibly be effectful equivalents of require, assume and assert in cats-effect Sync? It's the lowest part of the Typelevel hierarchy that has knowledge that E is Throwable and can raiseError I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's interesting :) We have ApplicativeThrow#catchNonFatal and there may be similar methods in there.


H.digest(key, ByteVector.fromLong(movingFactor)).map { hmac =>
val offset = hmac.last & 0xf

Expand Down