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
159
Issues
159
List
Board
Labels
Milestones
Merge Requests
8
Merge Requests
8
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
18127e38
Commit
18127e38
authored
Mar 10, 2018
by
Alexandre Delanoë
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[BUG] server with auth (ReaderT and RessourceT).
parent
bf65675c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
310 additions
and
10 deletions
+310
-10
package.yaml
package.yaml
+3
-1
Auth.hs
src/Gargantext/API/Auth.hs
+164
-3
Foundation.hs
src/Gargantext/API/Foundation.hs
+135
-0
Prelude.hs
src/Gargantext/Prelude.hs
+8
-6
No files found.
package.yaml
View file @
18127e38
...
@@ -78,10 +78,11 @@ library:
...
@@ -78,10 +78,11 @@ library:
-
filepath
-
filepath
-
fclabels
-
fclabels
-
fast-logger
-
fast-logger
# - haskell-gi-base
# - haskell-gi-base
-
http-conduit
-
http-conduit
-
http-api-data
-
http-api-data
-
http-types
-
http-types
-
http-client
-
hxt
-
hxt
-
ini
-
ini
-
jose-jwt
-
jose-jwt
...
@@ -115,6 +116,7 @@ library:
...
@@ -115,6 +116,7 @@ library:
-
servant-static-th
-
servant-static-th
-
split
-
split
-
swagger2
-
swagger2
-
stm
-
tagsoup
-
tagsoup
-
text-metrics
-
text-metrics
-
time
-
time
...
...
src/Gargantext/API/Auth.hs
View file @
18127e38
...
@@ -16,18 +16,179 @@ Main authorisation of Gargantext are managed in this module
...
@@ -16,18 +16,179 @@ Main authorisation of Gargantext are managed in this module
-- 2: Implement the Auth API backend
-- 2: Implement the Auth API backend
https://github.com/haskell-servant/servant-auth
https://github.com/haskell-servant/servant-auth
Credits: http://blog.wuzzeb.org/full-stack-web-haskell/libraries.html
-}
-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
module
Gargantext.API.Auth
module
Gargantext.API.Auth
where
where
--import Gargantext.Prelude
------------------------------------------------------------------------
import
GHC.Int
(
Int64
)
import
GHC.Generics
(
Generic
)
import
Control.Lens
hiding
((
.=
))
import
Control.Applicative
import
Control.Monad.Reader
import
Data.Aeson
import
qualified
Data.Aeson
as
Aeson
import
Data.Either
(
Either
(
Left
,
Right
))
import
Data.Time.Clock.POSIX
(
POSIXTime
(),
getPOSIXTime
)
import
Data.Text
import
Data.Text.Encoding
(
decodeUtf8
,
encodeUtf8
)
import
Data.ByteString.Lazy
(
fromStrict
,
toStrict
)
import
Servant
import
qualified
Jose.Jwa
as
Jose
import
qualified
Jose.Jwt
as
Jose
import
Gargantext.Prelude
hiding
(
drop
)
import
Gargantext.API
import
Gargantext.API.Settings
------------------------------------------------------------------------
type
UserId
=
Int
-- | User credentials extracted from the JWT token
--data Auth = Auth { username :: Text
--data Auth = Auth { username :: Text
-- , password :: Text
-- , password :: Text
-- } deriving (Generics)
-- } deriving (Generics)
data
UserCredentials
=
UserCredentials
{
_credId
::
UserId
,
_credEmail
::
Text
,
_credEmailVerified
::
Bool
}
deriving
(
Show
,
Eq
,
Generic
)
makeLenses
''
U
serCredentials
-- | There are two kinds of tokens. When reseting the password, we send
-- a token via email. When a user logs in, we send a token to the browser.
data
TokenAudience
=
TokenSentViaEmail
|
TokenSentToBrowser
|
TokenForLiveUser
deriving
(
Show
,
Eq
,
Generic
)
-- | The contents of the Jwt token is an encoding of this structure.
data
Token
=
Token
{
_tokenCreds
::
UserCredentials
,
_issuedP
::
POSIXTime
,
_expiredP
::
POSIXTime
,
_jwtAudience
::
TokenAudience
}
deriving
(
Show
,
Eq
,
Generic
)
makeLenses
''
T
oken
instance
Aeson
.
FromJSON
Token
where
parseJSON
=
Aeson
.
withObject
"web token"
$
\
o
->
do
aud
::
Text
<-
o
.:
"aud"
aud'
<-
case
aud
of
"mycompany:email"
->
pure
TokenSentViaEmail
"mycompany:web"
->
pure
TokenSentToBrowser
_
->
panic
"Invalid audience for token"
Token
<$>
(
UserCredentials
<$>
o
.:
"sub"
<*>
o
.:
"email"
<*>
o
.:
"email_verified"
)
<*>
(
fromInteger
<$>
o
.:
"iat"
)
<*>
(
fromInteger
<$>
o
.:
"exp"
)
<*>
pure
aud'
instance
Aeson
.
ToJSON
Token
where
toJSON
(
Token
ucreds
i
e
a
)
=
Aeson
.
object
[
"sub"
.=
(
ucreds
^.
credId
)
,
"email"
.=
(
ucreds
^.
credEmail
)
,
"email_verified"
.=
(
ucreds
^.
credEmailVerified
)
,
"iat"
.=
(
round
i
::
Int64
)
,
"exp"
.=
(
round
e
::
Int64
)
,
"aud"
.=
case
a
of
TokenSentViaEmail
->
pack
"mycompany:email"
TokenSentToBrowser
->
pack
"mycompany:web"
TokenForLiveUser
->
pack
"mycompany:live"
]
newtype
UnverifiedJwtToken
=
UnverifiedJwtToken
Text
deriving
(
Show
)
-- deriving (Show, FromHttpApiData)
type
JwtAuthHeader
=
Header
"authorization"
UnverifiedJwtToken
type
MyAPIWithAuth
=
JwtAuthHeader
:>
GargAPI
-- Datastorage
--isMemberOfTeam :: UserCredentials -> Team -> Bool
--user `isMemberOfTeam` team = {- implementation here -}
--
--loadTeam :: UserCredentials -> TeamKey -> SqlPersistM Team
--loadTeam ucreds teamkey = do
-- mteam <- get teamkey
-- case mteam of
-- Nothing -> throwM DocumentNotFound
-- Just t | ucreds `isMemberOfTeam` t -> return t
-- | otherwise -> throwM Unauthorized
createJwt
::
TokenAudience
->
UserCredentials
->
Env
->
Servant
.
Handler
UnverifiedJwtToken
createJwt
aud
ucreds
env
=
do
now
<-
liftIO
getPOSIXTime
expire
<-
pure
$
case
aud
of
TokenSentViaEmail
->
15
*
60
-- 15 minutes
TokenForLiveUser
->
60
*
60
-- 1 hour
_
->
60
*
60
let
key
=
env
^.
settings
.
jwtSecret
let
token
=
Token
{
_tokenCreds
=
ucreds
,
_issuedP
=
now
,
_expiredP
=
now
+
expire
,
_jwtAudience
=
aud
}
mjwt
<-
liftIO
$
Jose
.
encode
[
key
]
(
Jose
.
JwsEncoding
Jose
.
HS256
)
(
Jose
.
Claims
$
toStrict
$
Aeson
.
encode
token
)
case
mjwt
of
Left
_
->
throwError
err500
{
errBody
=
"Unable to authenticate"
}
Right
jwt
->
pure
$
UnverifiedJwtToken
$
decodeUtf8
$
Jose
.
unJwt
jwt
verifyJwt
::
UnverifiedJwtToken
->
Env
->
Servant
.
Handler
Token
verifyJwt
(
UnverifiedJwtToken
unverifiedText
)
env
=
do
key
<-
pure
$
env
^.
settings
.
jwtSecret
mjwtContent
<-
liftIO
$
Jose
.
decode
[
key
]
(
Just
$
Jose
.
JwsEncoding
Jose
.
HS256
)
$
encodeUtf8
unverifiedText
jwt
<-
case
mjwtContent
of
Right
(
Jose
.
Jws
(
_
,
jwt
))
->
pure
$
jwt
_
->
throwError
err401
{
errBody
=
"Invalid javascript web token"
}
case
Aeson
.
eitherDecode
(
fromStrict
jwt
)
of
Left
_
->
throwError
err401
{
errBody
=
"Unable to parse jwt claims"
}
Right
token
->
do
now
<-
liftIO
getPOSIXTime
when
(
token
^.
expiredP
<=
now
)
$
throwError
err401
{
errBody
=
"Expired jwt token"
}
pure
token
-- | Verify and decode a token
verifyWebJwt
::
Maybe
UnverifiedJwtToken
->
Env
->
Servant
.
Handler
(
Maybe
Token
)
verifyWebJwt
Nothing
_
=
return
Nothing
verifyWebJwt
(
Just
(
UnverifiedJwtToken
x
))
env
=
do
let
unverifiedToken
=
if
"Bearer "
`
isPrefixOf
`
x
then
drop
7
x
else
x
token
<-
verifyJwt
(
UnverifiedJwtToken
unverifiedToken
)
env
case
token
^.
jwtAudience
of
TokenSentViaEmail
->
throwError
err403
{
errBody
=
"Cannot use email token for authentication"
}
TokenForLiveUser
->
pure
(
Just
token
)
_
->
pure
Nothing
src/Gargantext/API/Foundation.hs
0 → 100644
View file @
18127e38
{-|
Module : Gargantext.API.Foundation
Description : Handler of the API (Server and Client)
Copyright : (c) CNRS, 2017-Present
License : AGPL + CECILL v3
Maintainer : team@gargantext.org
Stability : experimental
Portability : POSIX
Inspired by : http://blog.wuzzeb.org/full-stack-web-haskell/server.html
-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
------------------------------------------------------------------------
module
Gargantext.API.Foundation
where
------------------------------------------------------------------------
import
System.Log.FastLogger
import
Control.Applicative
import
Control.Lens
import
Control.Monad
import
Control.Monad.Base
import
Control.Monad.Error.Class
import
Control.Monad.Logger
import
Control.Monad.Reader
import
Control.Monad.Trans.Except
import
Control.Monad.Trans.Resource
-- import Control.Natural ((:~>))
import
Data.Maybe
-- import Database.PostgreSQL.Simple
import
Servant
-- import Gargantext.API
import
Gargantext.API.Auth
import
Gargantext.API.Settings
import
Gargantext.Prelude
------------------------------------------------------------------------
------------------------------------------------------------------------
newtype
MyServer
a
=
MyServer
{
myServerM
::
ReaderT
(
Env
,
Maybe
Token
)
(
ResourceT
(
ExceptT
ServantErr
IO
)
)
a
}
deriving
(
Functor
,
Applicative
,
Monad
,
MonadIO
)
deriving
instance
MonadError
ServantErr
MyServer
instance
MonadBase
IO
MyServer
where
liftBase
=
liftIO
instance
MonadReader
Env
MyServer
where
ask
=
MyServer
(
fst
<$>
ask
)
local
f
(
MyServer
r
)
=
MyServer
(
local
(
\
(
e
,
t
)
->
(
f
e
,
t
))
r
)
instance
MonadLogger
MyServer
where
monadLoggerLog
loc
source
ll
msg
=
do
limit
<-
view
(
settings
.
logLevelLimit
)
out
<-
view
logger
when
(
ll
>=
limit
)
$
liftIO
$
pushLogStr
out
$
defaultLogStr
loc
source
ll
$
toLogStr
msg
------------------------------------------------------------------------
getToken
::
MyServer
(
Maybe
Token
)
getToken
=
MyServer
(
snd
<$>
ask
)
userRequired
::
MyServer
UserCredentials
userRequired
=
do
mt
<-
getToken
case
mt
of
Nothing
->
throwError
$
err401
{
errBody
=
"No Authorization header in request"
}
Just
t
->
return
$
t
^.
tokenCreds
runDB
::
(
a
->
b
)
->
MyServer
b
runDB
_
=
undefined
{- access pool from env, run action -}
--------------------------------------------------------------------------
--------------------------------------------------------------------------
--------------------------------------------------------------------------
--------------------------------------------------------------------------
--
-- | very basic Example for testing purpose
-- type MyAPI = TeamAPI -- :<|> UserAPI :<|> ...
type
TeamAPI
=
GetUserRoute
-- :<|>
type
GetUserRoute
=
"team"
:>
Capture
"teamkey"
Int
:>
Get
'[
J
SON
]
Int
--type GetTeamRoute = "team" :> Capture "teamkey" TeamKey :> Get '[JSON] Team
myServerAPI
::
Proxy
TeamAPI
myServerAPI
=
Proxy
--gargServer' :: ServerT MyAPI MyServer
--gargServer' = teamServer
teamServer
::
ServerT
TeamAPI
MyServer
teamServer
=
getTeamR
-- :<|> createTeamR :<|> updateTeamR :<|> getAllTeamsR
getTeamR
::
Int
->
MyServer
Int
getTeamR
_
=
do
pure
1
---- Note that @type MyAPIWithAuth = JwtAuthHeader :> MyAPI@ so that
---- @Server MyAPIWithAuth@ expands to @Maybe UnverifiedJwtToken -> Server MyAPI@.
--myServerWithAuth :: Env -> Server MyAPIWithAuth
----myServerWithAuth env unverifiedJwt = enter (myServerNat env unverifiedJwt) myServer
--myServerWithAuth :: forall a. Env -> Maybe UnverifiedJwtToken -> ServerT (MyServer a) Handler
myServerWithAuth
::
Env
->
Maybe
UnverifiedJwtToken
->
Int
->
Handler
Int
myServerWithAuth
env
unverifiedJwt
=
hoistServer
myServerAPI
(
nt
env
unverifiedJwt
)
teamServer
-- nt :: Applicative f => Env -> p -> MyServer a -> f (ExceptT ServantErr IO a)
nt
::
Env
->
Maybe
UnverifiedJwtToken
->
MyServer
a
->
Handler
(
ExceptT
ServantErr
IO
a
)
nt
env
_
s
=
pure
$
runResourceT
(
runReaderT
(
myServerM
s
)
(
env
,
mtoken'
))
where
mtoken'
::
Maybe
Token
mtoken'
=
undefined
src/Gargantext/Prelude.hs
View file @
18127e38
...
@@ -15,17 +15,19 @@ module Gargantext.Prelude
...
@@ -15,17 +15,19 @@ module Gargantext.Prelude
)
)
where
where
import
Protolude
(
Bool
(
True
,
False
),
Int
,
Double
,
Integer
import
Protolude
(
Eq
,
Bool
(
True
,
False
),
Int
,
Double
,
Integer
,
Fractional
,
Num
,
Maybe
(
Just
,
Nothing
)
,
Fractional
,
Num
,
Maybe
(
Just
,
Nothing
)
,
Floating
,
Char
,
IO
,
Floating
,
Char
,
IO
,
pure
,
(
<$>
),
panic
,
pure
,
return
,
(
<$>
),
panic
,
Ord
,
Integral
,
Foldable
,
RealFrac
,
Monad
,
filter
,
Ord
,
Integral
,
Foldable
,
RealFrac
,
Monad
,
filter
,
reverse
,
map
,
zip
,
drop
,
take
,
zipWith
,
reverse
,
map
,
zip
,
drop
,
take
,
zipWith
,
sum
,
fromIntegral
,
length
,
fmap
,
sum
,
fromIntegral
,
fromInteger
,
length
,
fmap
,
takeWhile
,
sqrt
,
undefined
,
identity
,
takeWhile
,
sqrt
,
undefined
,
identity
,
abs
,
maximum
,
minimum
,
return
,
snd
,
truncate
,
abs
,
maximum
,
minimum
,
truncate
,
(
+
),
(
*
),
(
/
),
(
-
),
(
.
),
(
>=
),
(
$
),
(
**
),
(
^
),
(
<
),
(
>
),
(
==
),
(
<>
)
,
fst
,
snd
,
(
&&
),
(
||
),
not
,
(
+
),
(
*
),
(
/
),
(
-
),
(
.
),
(
>=
),
(
<=
)
,
(
$
),
(
**
),
(
^
),
(
<
),
(
>
),
(
==
),
(
<>
)
,
(
&&
),
(
||
),
not
,
round
,
toS
,
toS
)
)
...
...
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