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
150
Issues
150
List
Board
Labels
Milestones
Merge Requests
5
Merge Requests
5
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