{-# LANGUAGE OverloadedStrings #-}

{- |

Authentication functions using 'MonadSmtp'.

Aims to eventually implement 
three widely-used SMTP authentication mechanisms:
Login, Plain, and Cram-MD5.

Currently, Login is implemented.

/TODO/: Add Cram-MD5, other auth methods.

-}

{-

Refs: see RFCs,
      https://networking.ringofsaturn.com/Protocols/smtpauth.php

SMTP authentication standards, courtesy of Wikipedia
(https://en.wikipedia.org/wiki/SMTP_Authentication):

-   PLAIN (Uses Base64 encoding)
-   LOGIN (Uses Base64 encoding)
-   GSSAPI (Generic Security Services Application Program Interface)
-   DIGEST-MD5 (Digest access authentication)
-   MD5
-   CRAM-MD5
-   OAUTH10A (OAuth 1.0a HMAC-SHA1 tokens as defined in RFC 5849)
-   OAUTHBEARER (OAuth 2.0 bearer tokens as defined in RFC 6750)

TODO: use text-conversions-0.3.0?
Has a dedicated Base64 newtype. 

TODO: add tests for the auth funcs.

-}

module Network.Mail.Assumpta.Auth
  (
    login
  , UserName
  , Password
  )
  where

import           Crypto.Hash (MD5)
import           Crypto.MAC.HMAC (hmac, HMAC)

import           Data.ByteArray.Encoding (convertToBase, Base(Base16))
import qualified Data.ByteString as BS
import           Data.ByteString (ByteString)
import qualified Data.ByteString.Base64 as B64  (encode)
import qualified Data.ByteString.Char8 as B8

import Network.Mail.Assumpta.MonadSmtp

--  TODO - implement and SmtpAuth data type.


type UserName = ByteString
type Password = ByteString

-- newtype Password = Password ByteString
--instance Show Password where
--  show _ = "****"  

-- | Perform LOGIN authentication using the specified username
-- and password.
--
-- /TODO/: better reporting of errors when login fails.
-- Currently just throws an 'SMTPError' indicating that
-- @expectCode 235@ failed.
login :: MonadSmtp m => UserName -> Password -> m ()
login username password = do
  sendLine "AUTH LOGIN"
  expectCode 334
  let username' = B64.encode username
      password' = B64.encode password
  sendLine username'
  expectCode 334
  sendLine password'
  expectCode 235

-- | Not implemented yet -- TODO 
cramMd5 :: MonadSmtp m => UserName -> Password -> m ()
cramMd5 _username _password = do
  sendLine "XXXXX"
  undefined

-- cramMD5 function from smtp-mail by
-- Jason Hickner and Matt Parsons 
cramMD5 :: String -> String -> String -> ByteString
cramMD5 challenge user pass =
    B64.encode $ B8.unwords [user', convertToBase Base16 hmac']
  where
    challenge' = toAscii challenge
    user'      = toAscii user
    pass'      = toAscii pass

    hmac' :: HMAC MD5
    hmac'      = hmac challenge' pass'

    toAscii :: String -> ByteString
    toAscii = BS.pack . map (toEnum.fromEnum)