Commit d0c5fec3 authored by Alfredo Di Napoli's avatar Alfredo Di Napoli

Support InternalAuthenticationError

parent 988c0f97
...@@ -53,7 +53,7 @@ import Gargantext.API.Admin.EnvTypes (GargJob(..), Env) ...@@ -53,7 +53,7 @@ import Gargantext.API.Admin.EnvTypes (GargJob(..), Env)
import Gargantext.API.Admin.Orchestrator.Types (JobLog(..), AsyncJobs) import Gargantext.API.Admin.Orchestrator.Types (JobLog(..), AsyncJobs)
import Gargantext.API.Admin.Types import Gargantext.API.Admin.Types
import Gargantext.API.Auth.PolicyCheck import Gargantext.API.Auth.PolicyCheck
import Gargantext.API.Prelude (joseError, HasServerError, GargServerC, GargServer, _ServerError, GargM) import Gargantext.API.Prelude (authenticationError, HasServerError, GargServerC, GargServer, _ServerError, GargM)
import Gargantext.Core.Mail (MailModel(..), mail) import Gargantext.Core.Mail (MailModel(..), mail)
import Gargantext.Core.Mail.Types (mailSettings) import Gargantext.Core.Mail.Types (mailSettings)
import Gargantext.Core.Types.Individu (User(..), Username, GargPassword(..)) import Gargantext.Core.Types.Individu (User(..), Username, GargPassword(..))
...@@ -78,7 +78,7 @@ import Gargantext.API.Errors ...@@ -78,7 +78,7 @@ import Gargantext.API.Errors
-- | Main functions of authorization -- | Main functions of authorization
makeTokenForUser :: (HasSettings env, HasJoseError err) makeTokenForUser :: (HasSettings env, HasAuthenticationError err)
=> NodeId => NodeId
-> UserId -> UserId
-> Cmd' env err Token -> Cmd' env err Token
...@@ -86,10 +86,10 @@ makeTokenForUser nodeId userId = do ...@@ -86,10 +86,10 @@ makeTokenForUser nodeId userId = do
jwtS <- view $ settings . jwtSettings jwtS <- view $ settings . jwtSettings
e <- liftBase $ makeJWT (AuthenticatedUser nodeId userId) jwtS Nothing e <- liftBase $ makeJWT (AuthenticatedUser nodeId userId) jwtS Nothing
-- TODO-SECURITY here we can implement token expiration ^^. -- TODO-SECURITY here we can implement token expiration ^^.
either joseError (pure . toStrict . LE.decodeUtf8) e either (authenticationError . LoginFailed nodeId userId) (pure . toStrict . LE.decodeUtf8) e
-- TODO not sure about the encoding... -- TODO not sure about the encoding...
checkAuthRequest :: ( HasSettings env, HasJoseError err, DbCmd' env err m ) checkAuthRequest :: ( HasSettings env, HasAuthenticationError err, DbCmd' env err m )
=> Username => Username
-> GargPassword -> GargPassword
-> m CheckAuth -> m CheckAuth
...@@ -114,7 +114,7 @@ checkAuthRequest couldBeEmail (GargPassword p) = do ...@@ -114,7 +114,7 @@ checkAuthRequest couldBeEmail (GargPassword p) = do
token <- makeTokenForUser nodeId userLight_id token <- makeTokenForUser nodeId userLight_id
pure $ Valid token nodeId userLight_id pure $ Valid token nodeId userLight_id
auth :: (HasSettings env, HasJoseError err, DbCmd' env err m) auth :: (HasSettings env, HasAuthenticationError err, DbCmd' env err m)
=> AuthRequest -> m AuthResponse => AuthRequest -> m AuthResponse
auth (AuthRequest u p) = do auth (AuthRequest u p) = do
checkAuthRequest' <- checkAuthRequest u p checkAuthRequest' <- checkAuthRequest u p
...@@ -233,11 +233,12 @@ forgotPasswordPost (ForgotPasswordRequest email) = do ...@@ -233,11 +233,12 @@ forgotPasswordPost (ForgotPasswordRequest email) = do
-- users' emails -- users' emails
pure $ ForgotPasswordResponse "ok" pure $ ForgotPasswordResponse "ok"
forgotPasswordGet :: (HasSettings env, CmdCommon env, HasJoseError err, HasServerError err) forgotPasswordGet :: (HasSettings env, CmdCommon env, HasAuthenticationError err, HasServerError err)
=> Maybe Text -> Cmd' env err ForgotPasswordGet => Maybe Text -> Cmd' env err ForgotPasswordGet
forgotPasswordGet Nothing = pure $ ForgotPasswordGet "" forgotPasswordGet Nothing = pure $ ForgotPasswordGet ""
forgotPasswordGet (Just uuid) = do forgotPasswordGet (Just uuid) = do
let mUuid = fromText uuid let mUuid = fromText uuid
-- FIXME(adn) Sending out \"not found\" is leaking information here, we ought to fix it.
case mUuid of case mUuid of
Nothing -> throwError $ _ServerError # err404 { errBody = "Not found" } Nothing -> throwError $ _ServerError # err404 { errBody = "Not found" }
Just uuid' -> do Just uuid' -> do
...@@ -249,7 +250,7 @@ forgotPasswordGet (Just uuid) = do ...@@ -249,7 +250,7 @@ forgotPasswordGet (Just uuid) = do
--------------------- ---------------------
forgotPasswordGetUser :: ( HasSettings env, CmdCommon env, HasJoseError err, HasServerError err) forgotPasswordGetUser :: ( HasSettings env, CmdCommon env, HasAuthenticationError err, HasServerError err)
=> UserLight -> Cmd' env err ForgotPasswordGet => UserLight -> Cmd' env err ForgotPasswordGet
forgotPasswordGetUser (UserLight { .. }) = do forgotPasswordGetUser (UserLight { .. }) = do
-- pick some random password -- pick some random password
......
...@@ -25,6 +25,7 @@ import Gargantext.Prelude hiding (reverse) ...@@ -25,6 +25,7 @@ import Gargantext.Prelude hiding (reverse)
import Servant.Auth.Server import Servant.Auth.Server
import Test.QuickCheck (elements, oneof) import Test.QuickCheck (elements, oneof)
import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary) import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
import qualified Crypto.JWT as Jose
--------------------------------------------------- ---------------------------------------------------
...@@ -70,6 +71,10 @@ instance ToSchema AuthenticatedUser where ...@@ -70,6 +71,10 @@ instance ToSchema AuthenticatedUser where
instance ToJWT AuthenticatedUser instance ToJWT AuthenticatedUser
instance FromJWT AuthenticatedUser instance FromJWT AuthenticatedUser
data AuthenticationError
= LoginFailed NodeId UserId Jose.Error
deriving (Show, Eq)
-- TODO-SECURITY why is the CookieSettings necessary? -- TODO-SECURITY why is the CookieSettings necessary?
type AuthContext = '[JWTSettings, CookieSettings] -- , BasicAuthCfg type AuthContext = '[JWTSettings, CookieSettings] -- , BasicAuthCfg
......
...@@ -26,6 +26,7 @@ import qualified Network.HTTP.Types.Status as HTTP ...@@ -26,6 +26,7 @@ import qualified Network.HTTP.Types.Status as HTTP
import qualified Data.Text as T import qualified Data.Text as T
import Gargantext.Database.Query.Tree hiding (treeError) import Gargantext.Database.Query.Tree hiding (treeError)
import Data.Validity ( prettyValidation ) import Data.Validity ( prettyValidation )
import Gargantext.API.Admin.Auth.Types
$(deriveHttpStatusCode ''BackendErrorCode) $(deriveHttpStatusCode ''BackendErrorCode)
...@@ -43,13 +44,22 @@ backendErrorToFrontendError = \case ...@@ -43,13 +44,22 @@ backendErrorToFrontendError = \case
$ FE_validation_error $ case prettyValidation validationError of $ FE_validation_error $ case prettyValidation validationError of
Nothing -> "unknown_validation_error" Nothing -> "unknown_validation_error"
Just v -> T.pack v Just v -> T.pack v
InternalJoseError _joseError InternalAuthenticationError authError
-> undefined -> authErrorToFrontendError authError
InternalServerError _internalServerError InternalServerError _internalServerError
-> undefined -> undefined
InternalJobError _jobError InternalJobError _jobError
-> undefined -> undefined
authErrorToFrontendError :: AuthenticationError -> FrontendError
authErrorToFrontendError = \case
-- For now, we ignore the Jose error, as they are too specific
-- (i.e. they should be logged internally to Sentry rather than shared
-- externall).
LoginFailed nid uid _
-> mkFrontendErr' "Invalid username/password, or invalid session token." $ FE_login_failed_error nid uid
nodeErrorToFrontendError :: NodeError -> FrontendError nodeErrorToFrontendError :: NodeError -> FrontendError
nodeErrorToFrontendError ne = case ne of nodeErrorToFrontendError ne = case ne of
NoListFound lid NoListFound lid
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Gargantext.API.Errors.Class where module Gargantext.API.Errors.Class where
import Control.Lens import Control.Lens
import Crypto.JOSE.Error as Jose import Gargantext.API.Admin.Auth.Types (AuthenticationError)
class HasJoseError e where class HasAuthenticationError e where
_JoseError :: Prism' e Jose.Error _AuthenticationError :: Prism' e AuthenticationError
...@@ -61,13 +61,13 @@ import Servant (ServerError) ...@@ -61,13 +61,13 @@ import Servant (ServerError)
import Servant.Job.Core import Servant.Job.Core
import Test.QuickCheck import Test.QuickCheck
import Test.QuickCheck.Instances.Text () import Test.QuickCheck.Instances.Text ()
import qualified Crypto.JWT as Jose
import qualified Data.Text as T import qualified Data.Text as T
import qualified Gargantext.Utils.Jobs.Monad as Jobs import qualified Gargantext.Utils.Jobs.Monad as Jobs
import qualified Servant.Job.Types as SJ import qualified Servant.Job.Types as SJ
import Text.Read (readMaybe) import Text.Read (readMaybe)
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import Data.Maybe import Data.Maybe
import Gargantext.API.Admin.Auth.Types (AuthenticationError)
-- | A 'WithStacktrace' carries an error alongside its -- | A 'WithStacktrace' carries an error alongside its
-- 'CallStack', to be able to print the correct source location -- 'CallStack', to be able to print the correct source location
...@@ -86,12 +86,12 @@ instance Exception e => Exception (WithStacktrace e) where ...@@ -86,12 +86,12 @@ instance Exception e => Exception (WithStacktrace e) where
-- | An internal error which can be emitted from the backend and later -- | An internal error which can be emitted from the backend and later
-- converted into a 'FrontendError', for later consumption. -- converted into a 'FrontendError', for later consumption.
data BackendInternalError data BackendInternalError
= InternalNodeError !NodeError = InternalNodeError !NodeError
| InternalTreeError !TreeError | InternalTreeError !TreeError
| InternalValidationError !Validation | InternalValidationError !Validation
| InternalJoseError !Jose.Error | InternalAuthenticationError !AuthenticationError
| InternalServerError !ServerError | InternalServerError !ServerError
| InternalJobError !Jobs.JobError | InternalJobError !Jobs.JobError
deriving (Show, Typeable) deriving (Show, Typeable)
makePrisms ''BackendInternalError makePrisms ''BackendInternalError
...@@ -122,8 +122,8 @@ instance HasTreeError BackendInternalError where ...@@ -122,8 +122,8 @@ instance HasTreeError BackendInternalError where
instance HasServerError BackendInternalError where instance HasServerError BackendInternalError where
_ServerError = _InternalServerError _ServerError = _InternalServerError
instance HasJoseError BackendInternalError where instance HasAuthenticationError BackendInternalError where
_JoseError = _InternalJoseError _AuthenticationError = _InternalAuthenticationError
-- | An error that can be returned to the frontend. It carries a human-friendly -- | An error that can be returned to the frontend. It carries a human-friendly
-- diagnostic, the 'type' of the error as well as some context-specific data. -- diagnostic, the 'type' of the error as well as some context-specific data.
...@@ -199,6 +199,17 @@ data instance ToFrontendErrorData 'EC_400__validation_error = ...@@ -199,6 +199,17 @@ data instance ToFrontendErrorData 'EC_400__validation_error =
FE_validation_error { validation_error :: T.Text } FE_validation_error { validation_error :: T.Text }
deriving (Show, Eq, Generic) deriving (Show, Eq, Generic)
--
-- authentication errors
--
data instance ToFrontendErrorData 'EC_403__login_failed_error =
FE_login_failed_error { lfe_node_id :: NodeId
, lfe_user_id :: UserId
}
deriving (Show, Eq, Generic)
-- --
-- Tree errors -- Tree errors
-- --
...@@ -267,6 +278,19 @@ instance FromJSON (ToFrontendErrorData 'EC_400__validation_error) where ...@@ -267,6 +278,19 @@ instance FromJSON (ToFrontendErrorData 'EC_400__validation_error) where
parseJSON (String txt) = pure $ FE_validation_error txt parseJSON (String txt) = pure $ FE_validation_error txt
parseJSON ty = typeMismatch "FE_validation_error" ty parseJSON ty = typeMismatch "FE_validation_error" ty
--
-- authentication errors
--
instance ToJSON (ToFrontendErrorData 'EC_403__login_failed_error) where
toJSON FE_login_failed_error{..} =
object [ "user_id" .= toJSON lfe_user_id, "node_id" .= toJSON lfe_node_id ]
instance FromJSON (ToFrontendErrorData 'EC_403__login_failed_error) where
parseJSON = withObject "FE_login_failed_error" $ \o -> do
lfe_user_id <- o .: "user_id"
lfe_node_id <- o .: "node_id"
pure FE_login_failed_error{..}
-- --
-- tree errors -- tree errors
...@@ -317,12 +341,19 @@ genFrontendErr be = do ...@@ -317,12 +341,19 @@ genFrontendErr be = do
EC_404__node_error_not_found EC_404__node_error_not_found
-> do nodeId <- arbitrary -> do nodeId <- arbitrary
pure $ mkFrontendErr' txt (FE_node_error_not_found nodeId) pure $ mkFrontendErr' txt (FE_node_error_not_found nodeId)
-- validation error -- validation error
EC_400__validation_error EC_400__validation_error
-> do let genValChain = oneof [ Violated <$> arbitrary, Location <$> arbitrary <*> genValChain ] -> do let genValChain = oneof [ Violated <$> arbitrary, Location <$> arbitrary <*> genValChain ]
chain <- listOf1 genValChain chain <- listOf1 genValChain
pure $ mkFrontendErr' txt $ FE_validation_error (T.pack $ fromMaybe "unknown_validation_error" $ prettyValidation $ Validation chain) pure $ mkFrontendErr' txt $ FE_validation_error (T.pack $ fromMaybe "unknown_validation_error" $ prettyValidation $ Validation chain)
-- authentication error
EC_403__login_failed_error
-> do nid <- arbitrary
uid <- arbitrary
pure $ mkFrontendErr' txt $ FE_login_failed_error nid uid
-- tree errors -- tree errors
EC_404__tree_error_root_not_found EC_404__tree_error_root_not_found
-> pure $ mkFrontendErr' txt $ FE_tree_error_root_not_found -> pure $ mkFrontendErr' txt $ FE_tree_error_root_not_found
...@@ -374,6 +405,11 @@ instance FromJSON FrontendError where ...@@ -374,6 +405,11 @@ instance FromJSON FrontendError where
(fe_data :: ToFrontendErrorData 'EC_400__validation_error) <- o .: "data" (fe_data :: ToFrontendErrorData 'EC_400__validation_error) <- o .: "data"
pure FrontendError{..} pure FrontendError{..}
-- authentication errors
EC_403__login_failed_error -> do
(fe_data :: ToFrontendErrorData 'EC_403__login_failed_error) <- o .: "data"
pure FrontendError{..}
-- tree errors -- tree errors
EC_404__tree_error_root_not_found -> do EC_404__tree_error_root_not_found -> do
(fe_data :: ToFrontendErrorData 'EC_404__tree_error_root_not_found) <- o .: "data" (fe_data :: ToFrontendErrorData 'EC_404__tree_error_root_not_found) <- o .: "data"
......
...@@ -23,6 +23,8 @@ data BackendErrorCode ...@@ -23,6 +23,8 @@ data BackendErrorCode
| EC_500__node_error_not_implemented_yet | EC_500__node_error_not_implemented_yet
-- validation errors -- validation errors
| EC_400__validation_error | EC_400__validation_error
-- authentication errors
| EC_403__login_failed_error
-- tree errors -- tree errors
| EC_404__tree_error_root_not_found | EC_404__tree_error_root_not_found
| EC_404__tree_error_empty_root | EC_404__tree_error_empty_root
......
...@@ -21,8 +21,8 @@ module Gargantext.API.Prelude ...@@ -21,8 +21,8 @@ module Gargantext.API.Prelude
where where
import Control.Lens ((#)) import Control.Lens ((#))
import Crypto.JOSE.Error as Jose
import Data.Aeson.Types import Data.Aeson.Types
import Gargantext.API.Admin.Auth.Types (AuthenticationError)
import Gargantext.API.Admin.Orchestrator.Types import Gargantext.API.Admin.Orchestrator.Types
import Gargantext.API.Admin.Types import Gargantext.API.Admin.Types
import Gargantext.API.Errors.Class import Gargantext.API.Errors.Class
...@@ -40,8 +40,8 @@ import Servant ...@@ -40,8 +40,8 @@ import Servant
import Servant.Job.Async import Servant.Job.Async
import Servant.Job.Core (HasServerError(..), serverError) import Servant.Job.Core (HasServerError(..), serverError)
joseError :: (MonadError e m, HasJoseError e) => Jose.Error -> m a authenticationError :: (MonadError e m, HasAuthenticationError e) => AuthenticationError -> m a
joseError = throwError . (_JoseError #) authenticationError = throwError . (_AuthenticationError #)
type HasJobEnv' env = HasJobEnv env JobLog JobLog type HasJobEnv' env = HasJobEnv env JobLog JobLog
...@@ -56,13 +56,13 @@ type EnvC env = ...@@ -56,13 +56,13 @@ type EnvC env =
) )
type ErrC err = type ErrC err =
( HasNodeError err ( HasNodeError err
, HasValidationError err , HasValidationError err
, HasTreeError err , HasTreeError err
, HasServerError err , HasServerError err
, HasJoseError err , HasAuthenticationError err
-- , ToJSON err -- TODO this is arguable -- , ToJSON err -- TODO this is arguable
, Exception err , Exception err
) )
type GargServerC env err m = type GargServerC env err m =
......
...@@ -74,6 +74,9 @@ instance DecodeScalar UserId where ...@@ -74,6 +74,9 @@ instance DecodeScalar UserId where
instance ResourceId UserId where instance ResourceId UserId where
isPositive = (> 0) . _UserId isPositive = (> 0) . _UserId
instance Arbitrary UserId where
arbitrary = UnsafeMkUserId . getPositive <$> arbitrary
instance DefaultFromField SqlInt4 UserId instance DefaultFromField SqlInt4 UserId
where where
defaultFromField = fromPGSFromField defaultFromField = fromPGSFromField
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment