packages feed

mariadb-haskell-0.1.0.0: src/Database/MySQL/Authentication.hs

-- | Explicit external authentication settings. PAM replies are cleartext and
-- require TLS or a Unix socket. GSSAPI uses existing system credentials, never
-- the SQL password, and requires an exact expected server principal.
--
-- Pass these options to @connectWithAuthentication@ in
-- "Database.MySQL.Base" or "Database.MySQL.TLS". Defaults never invoke external
-- credential providers. The caller configures server plugins, PAM/realm policy
-- and credentials separately; the driver does not acquire an initial ticket.
-- GSSAPI requires @-threaded@ and macOS GSS.framework or MIT Kerberos on Linux;
-- Windows GSSAPI is unavailable. Use TLS for SQL traffic confidentiality even
-- when the login itself uses mutually authenticated Kerberos.
--
-- Dialogs allow at most 16 exchanges and 65,535-byte prompts/replies, with no
-- NUL in replies. Authentication messages have an additional bounded wire
-- budget. Callback exceptions retain their identity and close the transport.
-- See the <https://github.com/aantich/mariadb-haskell/blob/main/docs/USER_GUIDE.md user guide>.
module Database.MySQL.Authentication
    ( AuthenticationOptions(..)
    , defaultAuthenticationOptions
    , DialogPrompt(..)
    , passwordDialog
    ) where

import Data.ByteString (ByteString)

-- | Authentication-only settings, discarded after establishment. Callbacks run
-- on the connecting thread under the connection deadline; exceptions propagate
-- and close the transport. Do not log their replies or the SQL credentials.
data AuthenticationOptions = AuthenticationOptions
    { authenticationDialog :: Maybe (DialogPrompt -> IO ByteString)
      -- ^ Opt in to PAM/dialog and clear-password replies. No terminal fallback.
    , authenticationGSSAPIPrincipal :: Maybe ByteString
      -- ^ Require GSSAPI with this exact service principal, including realm.
      -- Password-only success and alternate plugin switches are rejected;
      -- no initial password proof is emitted before GSSAPI negotiation.
      -- Native GSSAPI requires an application built with @-threaded@ so a KDC
      -- call cannot prevent the connection deadline or cancellation handler.
    }

-- | Disable external credential use until the application opts in explicitly.
defaultAuthenticationOptions :: AuthenticationOptions
defaultAuthenticationOptions = AuthenticationOptions Nothing Nothing

-- | One MariaDB dialog question. Text is the server's uninterpreted prompt,
-- without its terminal NUL. Round numbers start at zero. A final question still
-- requires the server's OK before authentication succeeds.
data DialogPrompt = DialogPrompt
    { dialogText :: !ByteString
      -- ^ Uninterpreted server bytes; do not render as trusted terminal controls.
    , dialogEcho :: !Bool
      -- ^ True for a visible answer, False for a hidden credential prompt.
    , dialogLast :: !Bool
      -- ^ The server marks its last question; a terminal OK is still required.
    , dialogRound :: !Int
      -- ^ Zero-based exchange number, useful for explicit multi-factor policy.
    } deriving (Eq, Show)

-- | A noninteractive single-password PAM callback. Only the first hidden prompt
-- is accepted; a PIN, username or second factor needs an application callback.
-- Embedded NUL and response-size checks are applied by the authentication loop.
passwordDialog :: ByteString -> DialogPrompt -> IO ByteString
passwordDialog password prompt
    | not (dialogEcho prompt) && dialogRound prompt == 0 = pure password
    | otherwise = ioError (userError "PAM requires an application dialog callback")