packages feed

liboath-hs-0.0.1.0: src/LibOath.hs

{-# LANGUAGE PatternSynonyms #-}

{-|
Module      : LibOath
Description : This function exports a safe interface to the liboath library.
Copyright   : (c) Matt Parsons, 2017
License     : GPL-3
Maintainer  : parsonsmatt@gmail.com
Stability   : experimental
Portability : POSIX

This module exports a safe and idiomatic interface to liboath. It is designed to
be imported qualified.
-}
module LibOath
    ( module LibOath
    , Error(..)
    , pattern Base32
    , Base32(unBase32)
    ) where

import           Control.Exception.Safe
import           Control.Monad          (join)
import           Data.ByteString        (ByteString)
import qualified Data.ByteString        as BS
import           Data.Time.Clock.POSIX  (getPOSIXTime)
import qualified Foreign.C.String       as C
import qualified Foreign.Marshal.Alloc  as Alloc
import           Foreign.Storable       (peek)
import qualified Language.C.Inline      as C

import           LibOath.Internal

-- | Unwrap an 'Action', returning 'Either' an 'Error' caused by initialization
-- failure of
run :: Action a -> IO (Either Error a)
run = bracket initialize finalize . handle
  where
    initialize =
        fmap fromCInt oath_init
    finalize _ =
        fromCInt <$> oath_done
    handle action OK =
        Right <$> unAction action
    handle _ err =
        pure (Left err)

generateTOTPFromSecret :: ByteString -> IO (Either Error ByteString)
generateTOTPFromSecret bs = fmap join . run $ do
    eerrb32 <- decodeBase32 bs
    case eerrb32 of
        Left err  -> pure (Left err)
        Right b32 -> generateTOTP b32


-- | This function takes the given 'ByteString' and decodes into a 'Base32'
-- representation.
decodeBase32 :: ByteString -> Action (Either Error Base32)
decodeBase32 bs = Action $ do
    BS.useAsCString bs $ \cStr -> do
        secretPtr <- Alloc.malloc
        secretLenPtr <- Alloc.malloc
        oath_base32_decode cStr (fromIntegral (BS.length bs)) secretPtr secretLenPtr
            `whenOKM` do
                secretLen <- peek secretLenPtr
                secret <- peek secretPtr
                newBS <- BS.packCStringLen (secret, fromIntegral secretLen)
                pure (UnsafeBase32 newBS)

-- | This function generates a TOTP for the given 'Base32' with the default
-- configuration.
generateTOTP :: Base32 -> Action (Either Error ByteString)
generateTOTP (Base32 bs) = Action $ do
    BS.useAsCString bs $ \secret -> do
        let secretLen = fromIntegral (BS.length bs)
        -- assuming totp given
        -- this is going to
        now <- fromIntegral . floor <$> getPOSIXTime
        -- assume all defaults
        -- TODO: extract this to an options
        let whenTime = now
            t0 = 0
            timeStepSize = 0
            digits = 6
            window = 0
            iter = 1
        otpPtr <- C.newCString (replicate (fromIntegral digits + 1) ' ')
        oath_totp_generate
            secret secretLen
            (whenTime + iter * timeStepSize)
            (fromIntegral (case timeStepSize of C.CTime i -> i))
            t0
            digits
            otpPtr
            `whenOKM` do
                BS.packCString otpPtr

whenOK :: C.CInt -> IO a -> IO (Either Error a)
whenOK rc action =
    case fromCInt rc of
        OK  -> Right <$> action
        err -> pure (Left err)

whenOKM :: IO C.CInt -> IO a -> IO (Either Error a)
whenOKM mrc action = do
    rc <- mrc
    whenOK rc action