mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
server: webhook auth token caching
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7925 Co-authored-by: Sean Park-Ross <94021366+seanparkross@users.noreply.github.com> GitOrigin-RevId: eae1f4023a9e9144c9eb230529c214cb4327e44f
This commit is contained in:
parent
88488362e0
commit
b36971f637
@ -213,6 +213,59 @@ fields and a new websocket connection is established.
|
||||
|
||||
:::
|
||||
|
||||
## Caching webhook session variables
|
||||
|
||||
<div className="badge badge--primary heading-badge">
|
||||
Available on: Cloud Standard, Cloud Professional, Cloud Enterprise, Self-hosted Enterprise
|
||||
</div>
|
||||
|
||||
Webhook session variables can be cached in order to improve performance of the request. For caching, you need to
|
||||
return either:
|
||||
|
||||
- a `Cache-Control` variable, modeled on the
|
||||
[Cache-Control HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control), to specify a
|
||||
**relative** expiration time, in seconds.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"X-Hasura-User-Id": "26",
|
||||
"X-Hasura-Role": "user",
|
||||
"X-Hasura-Is-Owner": "false",
|
||||
"Cache-Control": "max-age=60"
|
||||
}
|
||||
```
|
||||
|
||||
- an `Expires` variable, modeled on the
|
||||
[Expires HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires), to specify an **absolute**
|
||||
expiration time. The expected format is `"%a, %d %b %Y %T GMT"`.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"X-Hasura-User-Id": "27",
|
||||
"X-Hasura-Role": "user",
|
||||
"X-Hasura-Is-Owner": "false",
|
||||
"Expires": "Mon, 30 Mar 2020 13:25:18 GMT"
|
||||
}
|
||||
```
|
||||
|
||||
:::tip Tip
|
||||
|
||||
The cache key is based on the following parameters:
|
||||
- Client headers
|
||||
- Graphql request
|
||||
|
||||
This means that the cache key will change if the graphql request changes. If you want to cache auth token based on
|
||||
client headers only, you can [omit the auth-hook request
|
||||
body](deployment/graphql-engine-flags/reference.mdx#send-request-body-to-auth-hook).
|
||||
|
||||
:::
|
||||
|
||||
## Auth webhook samples
|
||||
|
||||
We have put together a
|
||||
|
@ -177,6 +177,19 @@ requests.
|
||||
| **Default** | `GET` |
|
||||
| **Supported in** | CE, EE, Cloud |
|
||||
|
||||
### Send Request Body to Auth Hook
|
||||
|
||||
Whether or not to send the request body (graphql request/variables) to the auth hook in `POST` mode.
|
||||
|
||||
| | |
|
||||
| ------------------- | ----------------------------------------------- |
|
||||
| **Flag** | `--auth-hook-send-request-body <true-or-false>` |
|
||||
| **Env var** | `HASURA_GRAPHQL_AUTH_HOOK_SEND_REQUEST_BODY` |
|
||||
| **Accepted values** | Boolean |
|
||||
| **Options** | `true` or `false` |
|
||||
| **Default** | `true` |
|
||||
| **Supported in** | CE, EE, Cloud |
|
||||
|
||||
### BigQuery String Numeric Input
|
||||
|
||||
Stringify certain
|
||||
|
@ -2,6 +2,7 @@ module Hasura.HTTP
|
||||
( wreqOptions,
|
||||
HttpException (..),
|
||||
hdrsToText,
|
||||
textToHdrs,
|
||||
addDefaultHeaders,
|
||||
defaultHeaders,
|
||||
HttpResponse (..),
|
||||
@ -18,7 +19,7 @@ import Control.Exception (Exception (..), fromException)
|
||||
import Control.Lens hiding ((.=))
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Aeson.KeyMap qualified as KM
|
||||
import Data.CaseInsensitive (original)
|
||||
import Data.CaseInsensitive (mk, original)
|
||||
import Data.HashMap.Strict qualified as M
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Conversions (UTF8 (..), convertText)
|
||||
@ -40,6 +41,12 @@ hdrsToText hdrs =
|
||||
| (hdrName, hdrVal) <- hdrs
|
||||
]
|
||||
|
||||
textToHdrs :: [(Text, Text)] -> [HTTP.Header]
|
||||
textToHdrs hdrs =
|
||||
[ (mk (txtToBs hdrName), TE.encodeUtf8 hdrVal)
|
||||
| (hdrName, hdrVal) <- hdrs
|
||||
]
|
||||
|
||||
wreqOptions :: HTTP.Manager -> [HTTP.Header] -> Wreq.Options
|
||||
wreqOptions manager hdrs =
|
||||
Wreq.defaults
|
||||
|
@ -38,7 +38,9 @@ instance Show AuthHookType where
|
||||
|
||||
data AuthHook = AuthHook
|
||||
{ ahUrl :: Text,
|
||||
ahType :: AuthHookType
|
||||
ahType :: AuthHookType,
|
||||
-- | Whether to send the request body to the auth hook
|
||||
ahSendRequestBody :: Bool
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
@ -86,7 +88,16 @@ userInfoFromAuthHook logger manager hook reqHeaders reqs = do
|
||||
req
|
||||
& set HTTP.method "POST"
|
||||
& set HTTP.headers (addDefaultHeaders [contentType])
|
||||
& set HTTP.body (Just $ J.encode $ object ["headers" J..= headersPayload, "request" J..= reqs])
|
||||
& set
|
||||
HTTP.body
|
||||
( Just $
|
||||
J.encode $
|
||||
object
|
||||
( ["headers" J..= headersPayload]
|
||||
-- We will only send the request if `ahSendRequestBody` is set to true
|
||||
<> ["request" J..= reqs | ahSendRequestBody hook]
|
||||
)
|
||||
)
|
||||
HTTP.performRequest req' manager
|
||||
|
||||
logAndThrow :: HTTP.HttpException -> m a
|
||||
|
@ -236,7 +236,7 @@ mkConnParams ConnParamsRaw {..} = do
|
||||
-- | Fetch 'Auth.AuthHook' components from the environment and merge
|
||||
-- with the values consumed by the arg parser in 'AuthHookRaw'.
|
||||
mkAuthHook :: Monad m => AuthHookRaw -> WithEnvT m (Maybe Auth.AuthHook)
|
||||
mkAuthHook (AuthHookRaw mUrl mType) = do
|
||||
mkAuthHook (AuthHookRaw mUrl mType mSendRequestBody) = do
|
||||
mUrlEnv <- withOption mUrl authHookOption
|
||||
-- Also support HASURA_GRAPHQL_AUTH_HOOK_TYPE
|
||||
-- TODO (from master):- drop this in next major update <--- (NOTE: This comment is from 2020-08-21)
|
||||
@ -247,7 +247,18 @@ mkAuthHook (AuthHookRaw mUrl mType) = do
|
||||
<$> considerEnvs
|
||||
[_envVar authHookModeOption, "HASURA_GRAPHQL_AUTH_HOOK_TYPE"]
|
||||
)
|
||||
pure $ (`Auth.AuthHook` authMode) <$> mUrlEnv
|
||||
-- if authMode is `GET` then authSendRequestBody is set to `False`, otherwise we check for the config value
|
||||
authSendRequestBody <-
|
||||
case authMode of
|
||||
Auth.AHTGet -> pure False
|
||||
Auth.AHTPost ->
|
||||
onNothing
|
||||
mSendRequestBody
|
||||
( fromMaybe (_default authHookSendRequestBodyOption)
|
||||
<$> considerEnvs
|
||||
[_envVar authHookSendRequestBodyOption]
|
||||
)
|
||||
pure $ (\url -> Auth.AuthHook url authMode authSendRequestBody) <$> mUrlEnv
|
||||
|
||||
-- | Fetch 'Cors.CorsConfig' settings from the environment and merge
|
||||
-- with the settings consumed by the arg parser.
|
||||
|
@ -19,6 +19,7 @@ module Hasura.Server.Init.Arg.Command.Serve
|
||||
accessKeyOption,
|
||||
authHookOption,
|
||||
authHookModeOption,
|
||||
authHookSendRequestBodyOption,
|
||||
jwtSecretOption,
|
||||
unAuthRoleOption,
|
||||
corsDomainOption,
|
||||
@ -360,7 +361,7 @@ accessKeyOption =
|
||||
|
||||
parseAuthHook :: Opt.Parser Config.AuthHookRaw
|
||||
parseAuthHook =
|
||||
Config.AuthHookRaw <$> url <*> urlType
|
||||
Config.AuthHookRaw <$> url <*> urlType <*> sendRequestBody
|
||||
where
|
||||
url =
|
||||
Opt.optional $
|
||||
@ -377,6 +378,14 @@ parseAuthHook =
|
||||
<> Opt.metavar "<GET|POST>"
|
||||
<> Opt.help (Config._helpMessage authHookModeOption)
|
||||
)
|
||||
sendRequestBody :: Opt.Parser (Maybe Bool) =
|
||||
Opt.optional $
|
||||
Opt.option
|
||||
(Opt.eitherReader Env.fromEnv)
|
||||
( Opt.long "auth-hook-send-request-body"
|
||||
<> Opt.metavar "<true|false>"
|
||||
<> Opt.help (Config._helpMessage authHookSendRequestBodyOption)
|
||||
)
|
||||
|
||||
authHookOption :: Config.Option ()
|
||||
authHookOption =
|
||||
@ -394,6 +403,14 @@ authHookModeOption =
|
||||
Config._helpMessage = "HTTP method to use for authorization webhook (default: GET)"
|
||||
}
|
||||
|
||||
authHookSendRequestBodyOption :: Config.Option Bool
|
||||
authHookSendRequestBodyOption =
|
||||
Config.Option
|
||||
{ Config._default = True,
|
||||
Config._envVar = "HASURA_GRAPHQL_AUTH_HOOK_SEND_REQUEST_BODY",
|
||||
Config._helpMessage = "Send request body in POST method (default: true)"
|
||||
}
|
||||
|
||||
parseJwtSecret :: Opt.Parser (Maybe Auth.JWTConfig)
|
||||
parseJwtSecret =
|
||||
Opt.optional $
|
||||
@ -1183,6 +1200,7 @@ serveCmdFooter =
|
||||
Config.optionPP accessKeyOption,
|
||||
Config.optionPP authHookOption,
|
||||
Config.optionPP authHookModeOption,
|
||||
Config.optionPP authHookSendRequestBodyOption,
|
||||
Config.optionPP jwtSecretOption,
|
||||
Config.optionPP unAuthRoleOption,
|
||||
Config.optionPP corsDomainOption,
|
||||
|
@ -498,7 +498,8 @@ instance Hashable API
|
||||
|
||||
data AuthHookRaw = AuthHookRaw
|
||||
{ ahrUrl :: Maybe Text,
|
||||
ahrType :: Maybe Auth.AuthHookType
|
||||
ahrType :: Maybe Auth.AuthHookType,
|
||||
ahrSendRequestBody :: Maybe Bool
|
||||
}
|
||||
|
||||
-- | Sleep time interval for recurring activities such as (@'asyncActionsProcessor')
|
||||
|
@ -34,6 +34,7 @@ module Hasura.Server.Utils
|
||||
useBackendOnlyPermissionsHeader,
|
||||
userIdHeader,
|
||||
userRoleHeader,
|
||||
contentLengthHeader,
|
||||
sessionVariablePrefix,
|
||||
)
|
||||
where
|
||||
@ -92,6 +93,9 @@ userIdHeader = "x-hasura-user-id"
|
||||
requestIdHeader :: IsString a => a
|
||||
requestIdHeader = "x-request-id"
|
||||
|
||||
contentLengthHeader :: IsString a => a
|
||||
contentLengthHeader = "Content-Length"
|
||||
|
||||
useBackendOnlyPermissionsHeader :: IsString a => a
|
||||
useBackendOnlyPermissionsHeader = "x-hasura-use-backend-only-permissions"
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Hasura.Session
|
||||
( RoleName,
|
||||
mkRoleName,
|
||||
@ -32,6 +34,7 @@ where
|
||||
|
||||
import Autodocodec (HasCodec (codec), dimapCodec)
|
||||
import Data.Aeson
|
||||
import Data.Aeson.TH qualified as J
|
||||
import Data.Aeson.Types (Parser, toJSONKeyText)
|
||||
import Data.CaseInsensitive qualified as CI
|
||||
import Data.HashMap.Strict qualified as Map
|
||||
@ -178,6 +181,8 @@ data BackendOnlyFieldAccess
|
||||
| BOFADisallowed
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
$(J.deriveJSON hasuraJSON ''BackendOnlyFieldAccess)
|
||||
|
||||
instance Hashable BackendOnlyFieldAccess
|
||||
|
||||
data UserInfo = UserInfo
|
||||
@ -189,6 +194,8 @@ data UserInfo = UserInfo
|
||||
|
||||
instance Hashable UserInfo
|
||||
|
||||
$(J.deriveJSON hasuraJSON ''UserInfo)
|
||||
|
||||
class (Monad m) => UserInfoM m where
|
||||
askUserInfo :: m UserInfo
|
||||
|
||||
|
@ -615,7 +615,7 @@ fakeJWTConfig =
|
||||
in JWTConfig {..}
|
||||
|
||||
fakeAuthHook :: AuthHook
|
||||
fakeAuthHook = AuthHook "http://fake" AHTGet
|
||||
fakeAuthHook = AuthHook "http://fake" AHTGet False
|
||||
|
||||
mkRoleNameE :: Text -> RoleName
|
||||
mkRoleNameE = fromMaybe (error "fixme") . mkRoleName
|
||||
|
@ -53,7 +53,7 @@ emptyServeOptionsRaw =
|
||||
},
|
||||
rsoTxIso = Nothing,
|
||||
rsoAdminSecret = Nothing,
|
||||
rsoAuthHook = UUT.AuthHookRaw Nothing Nothing,
|
||||
rsoAuthHook = UUT.AuthHookRaw Nothing Nothing Nothing,
|
||||
rsoJwtSecret = Nothing,
|
||||
rsoUnAuthRole = Nothing,
|
||||
rsoCorsConfig = Nothing,
|
||||
@ -308,7 +308,7 @@ mkServeOptionsSpec =
|
||||
-- Then
|
||||
result = UUT.runWithEnv env (UUT.mkServeOptions @Hasura rawServeOptions)
|
||||
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTGet))
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTGet False))
|
||||
|
||||
Hspec.it "Env > Nothing" $ do
|
||||
let -- Given
|
||||
@ -321,11 +321,11 @@ mkServeOptionsSpec =
|
||||
-- Then
|
||||
result = UUT.runWithEnv env (UUT.mkServeOptions @Hasura rawServeOptions)
|
||||
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTPost))
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTPost True))
|
||||
|
||||
Hspec.it "Arg > Env" $ do
|
||||
let -- Given
|
||||
rawServeOptions = emptyServeOptionsRaw {UUT.rsoAuthHook = UUT.AuthHookRaw (Just "http://auth.hook.com") (Just Auth.AHTGet)}
|
||||
rawServeOptions = emptyServeOptionsRaw {UUT.rsoAuthHook = UUT.AuthHookRaw (Just "http://auth.hook.com") (Just Auth.AHTGet) Nothing}
|
||||
-- When
|
||||
env =
|
||||
[ (UUT._envVar UUT.authHookOption, "http://auth.hook.com"),
|
||||
@ -334,7 +334,7 @@ mkServeOptionsSpec =
|
||||
-- Then
|
||||
result = UUT.runWithEnv env (UUT.mkServeOptions @Hasura rawServeOptions)
|
||||
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTGet))
|
||||
fmap UUT.soAuthHook result `Hspec.shouldBe` Right (Just (Auth.AuthHook "http://auth.hook.com" Auth.AHTGet False))
|
||||
|
||||
Hspec.describe "soJwtSecret" $ do
|
||||
Hspec.it "Env > Nothing" $ do
|
||||
|
Loading…
Reference in New Issue
Block a user