Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
haskell-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
160
Issues
160
List
Board
Labels
Milestones
Merge Requests
14
Merge Requests
14
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gargantext
haskell-gargantext
Commits
fd1c5f67
Verified
Commit
fd1c5f67
authored
Jan 19, 2024
by
Przemyslaw Kaminski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[errors] GraphQL error format
parent
ec271ca6
Pipeline
#5514
passed with stages
in 132 minutes and 6 seconds
Changes
7
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
148 additions
and
103 deletions
+148
-103
Types.hs
src/Gargantext/API/Admin/Auth/Types.hs
+1
-0
Errors.hs
src/Gargantext/API/Errors.hs
+51
-37
Types.hs
src/Gargantext/API/Errors/Types.hs
+83
-61
Backend.hs
src/Gargantext/API/Errors/Types/Backend.hs
+1
-0
Team.hs
src/Gargantext/API/GraphQL/Team.hs
+6
-3
Utils.hs
src/Gargantext/API/GraphQL/Utils.hs
+1
-1
Server.hs
src/Gargantext/API/Server.hs
+5
-1
No files found.
src/Gargantext/API/Admin/Auth/Types.hs
View file @
fd1c5f67
...
...
@@ -65,6 +65,7 @@ instance FromJWT AuthenticatedUser
data
AuthenticationError
=
LoginFailed
NodeId
UserId
Jose
.
Error
|
InvalidUsernameOrPassword
|
UserNotAuthorized
UserId
Text
deriving
(
Show
,
Eq
)
-- TODO-SECURITY why is the CookieSettings necessary?
...
...
src/Gargantext/API/Errors.hs
View file @
fd1c5f67
...
...
@@ -12,6 +12,7 @@ module Gargantext.API.Errors (
-- * Conversion functions
,
backendErrorToFrontendError
,
frontendErrorToServerError
,
frontendErrorToGQLServerError
-- * Temporary shims
,
showAsServantJSONErr
...
...
@@ -20,6 +21,10 @@ module Gargantext.API.Errors (
import
Prelude
import
Control.Exception
import
Data.Aeson
qualified
as
JSON
import
Data.Text
qualified
as
T
import
Data.Text.Lazy
qualified
as
TL
import
Data.Text.Lazy.Encoding
qualified
as
TE
import
Data.Validity
(
prettyValidation
)
import
Gargantext.API.Admin.Auth.Types
import
Gargantext.API.Errors.Class
as
Class
...
...
@@ -28,20 +33,19 @@ import Gargantext.API.Errors.Types as Types
import
Gargantext.Database.Query.Table.Node.Error
hiding
(
nodeError
)
import
Gargantext.Database.Query.Tree
hiding
(
treeError
)
import
Gargantext.Utils.Jobs.Monad
(
JobError
(
..
))
import
Network.HTTP.Types.Status
qualified
as
HTTP
import
Servant.Server
import
qualified
Data.Aeson
as
JSON
import
qualified
Data.Text
as
T
import
qualified
Network.HTTP.Types.Status
as
HTTP
import
qualified
Data.Text.Lazy.Encoding
as
TE
import
qualified
Data.Text.Lazy
as
TL
$
(
deriveHttpStatusCode
''
B
ackendErrorCode
)
data
GargErrorScheme
=
-- | The old error scheme.
GES_old
-- | The new error scheme, that returns a 'FrontendError'.
-- | The new error scheme, that returns a 'FrontendError'.
|
GES_new
-- | Error scheme for GraphQL, has to be slightly different
-- {errors: [{message, extensions: { ... }}]}
-- https://spec.graphql.org/June2018/#sec-Errors
deriving
(
Show
,
Eq
)
-- | Transforms a backend internal error into something that the frontend
...
...
@@ -49,26 +53,56 @@ data GargErrorScheme
-- as we later encode this into a 'ServerError' in the main server handler.
backendErrorToFrontendError
::
BackendInternalError
->
FrontendError
backendErrorToFrontendError
=
\
case
InternalNodeError
nodeError
->
nodeErrorToFrontendError
nodeError
InternalTreeError
treeError
->
treeErrorToFrontendError
treeError
InternalValidationError
validationError
->
mkFrontendErr'
"A validation error occurred"
$
FE_validation_error
$
case
prettyValidation
validationError
of
Nothing
->
"unknown_validation_error"
Just
v
->
T
.
pack
v
InternalAuthenticationError
authError
->
authErrorToFrontendError
authError
Internal
ServerError
internalServer
Error
->
internalServerErrorToFrontendError
internalServer
Error
Internal
NodeError
node
Error
->
nodeErrorToFrontendError
node
Error
InternalJobError
jobError
->
jobErrorToFrontendError
jobError
InternalServerError
internalServerError
->
internalServerErrorToFrontendError
internalServerError
InternalTreeError
treeError
->
treeErrorToFrontendError
treeError
-- As this carries a 'SomeException' which might exposes sensible
-- information, we do not send to the frontend its content.
InternalUnexpectedError
_
->
let
msg
=
T
.
pack
$
"An unexpected error occurred. Please check your server logs."
in
mkFrontendErr'
msg
$
FE_internal_server_error
msg
InternalValidationError
validationError
->
mkFrontendErr'
"A validation error occurred"
$
FE_validation_error
$
case
prettyValidation
validationError
of
Nothing
->
"unknown_validation_error"
Just
v
->
T
.
pack
v
frontendErrorToGQLServerError
::
FrontendError
->
ServerError
frontendErrorToGQLServerError
fe
@
(
FrontendError
diag
ty
_
)
=
ServerError
{
errHTTPCode
=
HTTP
.
statusCode
$
backendErrorTypeToErrStatus
ty
,
errReasonPhrase
=
T
.
unpack
diag
,
errBody
=
JSON
.
encode
(
GraphQLError
fe
)
,
errHeaders
=
mempty
}
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
-- externally).
LoginFailed
nid
uid
_
->
mkFrontendErr'
"Invalid username/password, or invalid session token."
$
FE_login_failed_error
nid
uid
InvalidUsernameOrPassword
->
mkFrontendErr'
"Invalid username or password."
$
FE_login_failed_invalid_username_or_password
UserNotAuthorized
uId
msg
->
mkFrontendErr'
"User not authorized. "
$
FE_user_not_authorized
uId
msg
-- | Converts a 'FrontendError' into a 'ServerError' that the servant app can
-- return to the frontend.
frontendErrorToServerError
::
FrontendError
->
ServerError
frontendErrorToServerError
fe
@
(
FrontendError
diag
ty
_
)
=
ServerError
{
errHTTPCode
=
HTTP
.
statusCode
$
backendErrorTypeToErrStatus
ty
,
errReasonPhrase
=
T
.
unpack
diag
,
errBody
=
JSON
.
encode
fe
,
errHeaders
=
mempty
}
internalServerErrorToFrontendError
::
ServerError
->
FrontendError
internalServerErrorToFrontendError
=
\
case
...
...
@@ -86,16 +120,6 @@ jobErrorToFrontendError = \case
UnknownJob
jobId
->
mkFrontendErrNoDiagnostic
$
FE_job_unknown_job
jobId
JobException
err
->
mkFrontendErrNoDiagnostic
$
FE_job_generic_exception
(
T
.
pack
$
displayException
err
)
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
-- externally).
LoginFailed
nid
uid
_
->
mkFrontendErr'
"Invalid username/password, or invalid session token."
$
FE_login_failed_error
nid
uid
InvalidUsernameOrPassword
->
mkFrontendErr'
"Invalid username or password."
$
FE_login_failed_invalid_username_or_password
nodeErrorToFrontendError
::
NodeError
->
FrontendError
nodeErrorToFrontendError
ne
=
case
ne
of
NoListFound
lid
...
...
@@ -147,16 +171,6 @@ treeErrorToFrontendError te = case te of
EmptyRoot
->
mkFrontendErrShow
FE_tree_empty_root
TooManyRoots
roots
->
mkFrontendErrShow
$
FE_tree_too_many_roots
roots
-- | Converts a 'FrontendError' into a 'ServerError' that the servant app can
-- return to the frontend.
frontendErrorToServerError
::
FrontendError
->
ServerError
frontendErrorToServerError
fe
@
(
FrontendError
diag
ty
_
)
=
ServerError
{
errHTTPCode
=
HTTP
.
statusCode
$
backendErrorTypeToErrStatus
ty
,
errReasonPhrase
=
T
.
unpack
diag
,
errBody
=
JSON
.
encode
fe
,
errHeaders
=
mempty
}
showAsServantJSONErr
::
BackendInternalError
->
ServerError
showAsServantJSONErr
(
InternalNodeError
err
@
(
NoListFound
{}))
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoRootFound
{})
=
err404
{
errBody
=
JSON
.
encode
err
}
...
...
src/Gargantext/API/Errors/Types.hs
View file @
fd1c5f67
This diff is collapsed.
Click to expand it.
src/Gargantext/API/Errors/Types/Backend.hs
View file @
fd1c5f67
...
...
@@ -37,6 +37,7 @@ data BackendErrorCode
-- authentication errors
|
EC_403__login_failed_error
|
EC_403__login_failed_invalid_username_or_password
|
EC_403__user_not_authorized
-- tree errors
|
EC_404__tree_root_not_found
|
EC_404__tree_empty_root
...
...
src/Gargantext/API/GraphQL/Team.hs
View file @
fd1c5f67
...
...
@@ -16,6 +16,7 @@ module Gargantext.API.GraphQL.Team where
import
Data.Morpheus.Types
(
GQLType
,
ResolverM
)
import
Data.Text
qualified
as
T
import
Gargantext.API.Admin.Auth.Types
(
AuthenticationError
(
..
))
import
Gargantext.API.Admin.Types
(
HasSettings
)
import
Gargantext.API.Errors.Types
import
Gargantext.API.GraphQL.Types
(
GqlM
)
...
...
@@ -86,10 +87,12 @@ deleteTeamMembership TeamDeleteMArgs { token, shared_folder_id, team_node_id } =
[]
->
panicTrace
$
"[deleteTeamMembership] User with id "
<>
T
.
pack
(
show
$
uId
teamNode
)
<>
" doesn't exist."
((
_
,
node_u
)
:
_
)
->
do
testAuthUser
<-
lift
$
authUser
(
nId
node_u
)
token
case
testAuthUser
of
Invalid
->
panicTrace
"[deleteTeamMembership] failed to validate user"
lift
$
case
testAuthUser
of
-- Invalid -> panicTrace "[deleteTeamMembership] failed to validate user"
Invalid
->
do
throwError
$
InternalAuthenticationError
$
UserNotAuthorized
(
uId
node_u
)
"This user is not team owner"
Valid
->
do
lift
$
deleteMemberShip
[(
UnsafeMkNodeId
shared_folder_id
,
UnsafeMkNodeId
team_node_id
)]
deleteMemberShip
[(
UnsafeMkNodeId
shared_folder_id
,
UnsafeMkNodeId
team_node_id
)]
where
uId
Node
{
_node_user_id
}
=
_node_user_id
nId
Node
{
_node_id
}
=
_node_id
src/Gargantext/API/GraphQL/Utils.hs
View file @
fd1c5f67
...
...
@@ -10,6 +10,7 @@ Portability : POSIX
module
Gargantext.API.GraphQL.Utils
where
import
Control.Lens
((
^.
))
import
Control.Lens.Getter
(
view
)
import
Data.Morpheus.Types
(
GQLTypeOptions
,
fieldLabelModifier
)
import
Data.Text
qualified
as
T
...
...
@@ -20,7 +21,6 @@ import Gargantext.Database.Admin.Types.Node (NodeId)
import
Gargantext.Database.Prelude
(
Cmd
'
)
import
Gargantext.Prelude
import
Servant.Auth.Server
(
verifyJWT
,
JWTSettings
)
import
Control.Lens
((
^.
))
unPrefix
::
T
.
Text
->
GQLTypeOptions
->
GQLTypeOptions
unPrefix
prefix
options
=
options
{
fieldLabelModifier
=
nflm
}
...
...
src/Gargantext/API/Server.hs
View file @
fd1c5f67
...
...
@@ -67,13 +67,16 @@ server env = do
:<|>
hoistServerWithContext
(
Proxy
::
Proxy
GraphQL
.
API
)
(
Proxy
::
Proxy
AuthContext
)
(
transformJSON
errScheme
)
(
transformJSON
GQL
errScheme
)
GraphQL
.
api
:<|>
frontEndServer
where
transformJSON
::
forall
a
.
GargErrorScheme
->
GargM
Env
BackendInternalError
a
->
Handler
a
transformJSON
GES_old
=
Handler
.
withExceptT
showAsServantJSONErr
.
(`
runReaderT
`
env
)
.
logPanicErrors
transformJSON
GES_new
=
Handler
.
withExceptT
(
frontendErrorToServerError
.
backendErrorToFrontendError
)
.
(`
runReaderT
`
env
)
.
handlePanicErrors
transformJSONGQL
::
forall
a
.
GargErrorScheme
->
GargM
Env
BackendInternalError
a
->
Handler
a
transformJSONGQL
GES_old
=
Handler
.
withExceptT
showAsServantJSONErr
.
(`
runReaderT
`
env
)
.
logPanicErrors
transformJSONGQL
GES_new
=
Handler
.
withExceptT
(
frontendErrorToGQLServerError
.
backendErrorToFrontendError
)
.
(`
runReaderT
`
env
)
.
handlePanicErrors
handlePanicErrors
::
GargM
Env
BackendInternalError
a
->
GargM
Env
BackendInternalError
a
handlePanicErrors
h
=
h
`
catch
`
handleSomeException
...
...
@@ -104,3 +107,4 @@ logPanicErrors h = h `catch` handleSomeException
=
throwError
ber
-- re-throw the uncaught exception via the 'MonadError' instance
|
otherwise
=
throwM
se
-- re-throw the uncaught exception.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment