mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
Support for multiple domains (as CSV) in the `--cors-domain` flag and `HASURA_GRAPHQL_CORS_DOMAIN` env var. Following are all valid configurations (must include scheme and optional port): ```shell HASURA_GRAPHQL_CORS_DOMAIN="https://*.foo.bar.com:8080" HASURA_GRAPHQL_CORS_DOMAIN="https://*.foo.bar.com, http://*.localhost, https://example.com" HASURA_GRAPHQL_CORS_DOMAIN="*" HASURA_GRAPHQL_CORS_DOMAIN="http://example.com, http://*.localhost, http://localhost:3000, https://*.foo.bar.com, https://foo.bar.com" ``` **Note**: top-level domains are not considered as part of wildcard domains. You have to add them separately. E.g - `https://*.foo.com` doesn't include `https://foo.com`. The default (if the flag or env var is not specified) is `*`. Which means CORS headers are sent for all domains.
This commit is contained in:
parent
68bb898b24
commit
199a24d050
@ -119,6 +119,8 @@ WH_PID=""
|
||||
trap stop_services ERR
|
||||
trap stop_services INT
|
||||
|
||||
# test without access key
|
||||
|
||||
echo -e "\n<########## TEST GRAPHQL-ENGINE WITHOUT ACCESS KEYS ###########################################>\n"
|
||||
|
||||
"$GRAPHQL_ENGINE" serve > "$OUTPUT_FOLDER/graphql-engine.log" & PID=$!
|
||||
@ -131,7 +133,9 @@ kill -INT $PID
|
||||
sleep 4
|
||||
mv graphql-engine.tix graphql-engine-combined.tix || true
|
||||
|
||||
##########
|
||||
|
||||
# test with access key
|
||||
|
||||
echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY #####################################>\n"
|
||||
|
||||
export HASURA_GRAPHQL_ACCESS_KEY="HGE$RANDOM$RANDOM"
|
||||
@ -146,7 +150,9 @@ kill -INT $PID
|
||||
sleep 4
|
||||
combine_hpc_reports
|
||||
|
||||
##########
|
||||
|
||||
# test with jwt
|
||||
|
||||
echo -e "\n<########## TEST GRAPHQL-ENGINE WITH ACCESS KEY AND JWT #####################################>\n"
|
||||
|
||||
init_jwt
|
||||
@ -177,7 +183,24 @@ combine_hpc_reports
|
||||
|
||||
unset HASURA_GRAPHQL_JWT_SECRET
|
||||
|
||||
##########
|
||||
# test with CORS modes
|
||||
|
||||
echo -e "\n<########## TEST GRAPHQL-ENGINE WITH CORS DOMAINS ########>\n"
|
||||
export HASURA_GRAPHQL_CORS_DOMAIN="http://*.localhost, http://localhost:3000, https://*.foo.bar.com"
|
||||
|
||||
"$GRAPHQL_ENGINE" serve >> "$OUTPUT_FOLDER/graphql-engine.log" 2>&1 & PID=$!
|
||||
|
||||
wait_for_port 8080
|
||||
|
||||
pytest -vv --hge-url="$HGE_URL" --pg-url="$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ACCESS_KEY" --test-cors test_cors.py
|
||||
|
||||
kill -INT $PID
|
||||
sleep 4
|
||||
combine_hpc_reports
|
||||
unset HASURA_GRAPHQL_CORS_DOMAIN
|
||||
|
||||
|
||||
# webhook tests
|
||||
|
||||
if [ $EUID != 0 ] ; then
|
||||
echo -e "SKIPPING webhook based tests, as \nroot permission is required for running webhook tests (inorder to trust certificate authority)."
|
||||
@ -248,6 +271,8 @@ if [ "$RUN_WEBHOOK_TESTS" == "true" ] ; then
|
||||
combine_hpc_reports
|
||||
|
||||
kill $WH_PID
|
||||
|
||||
|
||||
fi
|
||||
|
||||
mv graphql-engine-combined.tix "$OUTPUT_FOLDER/graphql-engine.tix" || true
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ npm-debug.log
|
||||
test-server-output
|
||||
test-server-flags-output
|
||||
.vscode
|
||||
.idea
|
||||
|
@ -94,14 +94,33 @@ You can also set the access key using a flag to the command:
|
||||
Configure CORS
|
||||
--------------
|
||||
|
||||
By default, all CORS requests are allowed. To run Hasura with more restrictive CORS settings, use the ``--cors-domain`` flag.
|
||||
By default, all CORS requests to Hasura GraphQL engine are allowed. To run with more restrictive CORS settings,
|
||||
use the ``--cors-domain`` flag or the ``HASURA_GRAPHQL_CORS_DOMAIN`` ENV variable. The default value is ``*``,
|
||||
which means CORS headers are sent for all domains.
|
||||
|
||||
For example:
|
||||
Scheme + host with optional wildcard + optional port has to be mentioned.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -P -d hasura/graphql-engine:latest graphql-engine \
|
||||
--database-url postgres://username:password@host:5432/dbname \
|
||||
serve \
|
||||
--access-key XXXXXXXXXXXXXXXX
|
||||
--cors-domain https://mywebsite.com:8090
|
||||
# Accepts from https://app.foo.bar.com , https://api.foo.bar.com etc.
|
||||
HASURA_GRAPHQL_CORS_DOMAIN="https://*.foo.bar.com"
|
||||
|
||||
# Accepts from https://app.foo.bar.com:8080 , http://api.foo.bar.com:8080,
|
||||
# http://app.localhost, http://api.localhost, http://localhost:3000,
|
||||
# http://example.com etc.
|
||||
HASURA_GRAPHQL_CORS_DOMAIN="https://*.foo.bar.com:8080, http://*.localhost, http://localhost:3000, http://example.com"
|
||||
|
||||
# Accepts from all domain
|
||||
HASURA_GRAPHQL_CORS_DOMAIN="*"
|
||||
|
||||
# Accepts only from http://example.com
|
||||
HASURA_GRAPHQL_CORS_DOMAIN="http://example.com"
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Top-level domains are not considered as part of wildcard domains. You
|
||||
have to add them separately. E.g - ``https://*.foo.com`` doesn't include
|
||||
``https://foo.com``.
|
@ -12,18 +12,30 @@ Every GraphQL engine command is structured as:
|
||||
|
||||
$ graphql-engine <server-flags> serve <command-flags>
|
||||
|
||||
The flags can be passed as ENV variables as well.
|
||||
|
||||
Server flags
|
||||
^^^^^^^^^^^^
|
||||
|
||||
For ``graphql-engine`` command these are the flags available
|
||||
For ``graphql-engine`` command these are the flags and ENV variables available:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
--database-url Postgres database URL
|
||||
<postgres/postgresql>://<user>:<password>@<host>:<port>/<db-name>
|
||||
Example: postgres://admin:mypass@mydomain.com:5432/mydb
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
Or either you can specify following options
|
||||
* - Flag
|
||||
- ENV variable
|
||||
- Description
|
||||
|
||||
* - ``--database-url <DB_URL>``
|
||||
- ``HASURA_GRAPHQL_DATABASE_URL``
|
||||
- Postgres database URL:
|
||||
|
||||
``postgres://<user>:<password>@<host>:<port>/<db-name>``
|
||||
|
||||
Example: ``postgres://admin:mypass@mydomain.com:5432/mydb``
|
||||
|
||||
Or you can specify following options *(only via flags)*
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
@ -33,123 +45,100 @@ Or either you can specify following options
|
||||
-p, --password Password of the user
|
||||
-d, --dbname Database name to connect to
|
||||
|
||||
|
||||
Command flags
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
For ``serve`` subcommand these are the flags available
|
||||
For ``serve`` sub-command these are the flags and ENV variables available:
|
||||
|
||||
.. code-block:: none
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
--server-host IP address of network interface that graphql-engine will listen on (default: '*', all interfaces)
|
||||
* - Flag
|
||||
- ENV variable
|
||||
- Description
|
||||
|
||||
--server-port Port on which graphql-engine should be served (default: 8080)
|
||||
* - ``--server-port <PORT>``
|
||||
- ``HASURA_GRAPHQL_SERVER_PORT``
|
||||
- Port on which graphql-engine should be served (default: 8080)
|
||||
|
||||
--access-key Secret access key, required to access this instance.
|
||||
If specified client needs to send 'X-Hasura-Access-Key'
|
||||
header
|
||||
* - ``--server-host <HOST>``
|
||||
- ``HASURA_GRAPHQL_SERVER_HOST``
|
||||
- Host on which graphql-engine will listen (default: ``*``)
|
||||
|
||||
--cors-domain The domain, including sheme and port, to allow CORS for
|
||||
* - ``--enable-console <true|false>``
|
||||
- ``HASURA_GRAPHQL_ENABLE_CONSOLE``
|
||||
- Enable the Hasura Console (served by the server on ``/`` and ``/console``)
|
||||
|
||||
--disable-cors Disable CORS handling
|
||||
* - ``--access-key <SECRET_ACCESS_KEY>``
|
||||
- ``HASURA_GRAPHQL_ACCESS_KEY``
|
||||
- Secret access key, for admin access to this instance. This is mandatory
|
||||
when you use webhook or JWT.
|
||||
|
||||
--auth-hook The authentication webhook, required to authenticate
|
||||
incoming request
|
||||
* - ``--auth-hook <WEBHOOK_URL>``
|
||||
- ``HASURA_GRAPHQL_AUTH_HOOK``
|
||||
- URL of the authorization webhook required to authorize requests.
|
||||
See auth webhooks docs for more details.
|
||||
|
||||
--auth-hook-mode The authentication webhook mode. GET|POST (default: GET)
|
||||
* - ``--auth-hook-mode <GET|POST>``
|
||||
- ``HASURA_GRAPHQL_AUTH_HOOK_MODE``
|
||||
- HTTP method to use for the authorization webhook (default: GET)
|
||||
|
||||
--jwt-secret The JSON containing type and the JWK used for
|
||||
verifying. e.g: `{"type": "HS256", "key":
|
||||
"<your-hmac-shared-secret>"}`,`{"type": "RS256",
|
||||
"key": "<your-PEM-RSA-public-key>"}
|
||||
* - ``--jwt-secret <JSON_CONFIG>``
|
||||
- ``HASURA_GRAPHQL_JWT_SECRET``
|
||||
- A JSON string containing type and the JWK used for verifying (and other
|
||||
optional details).
|
||||
Example: ``{"type": "HS256", "key": "3bd561c37d214b4496d09049fadc542c"}``.
|
||||
See the JWT docs for more details.
|
||||
|
||||
--unauthorized-role Unauthorized role, used when access-key is not sent in
|
||||
access-key only mode or "Authorization" header is absent
|
||||
in JWT mode
|
||||
* - ``--unauthorized-role <ROLE>``
|
||||
- ``HASURA_GRAPHQL_UNAUTHORIZED_ROLE``
|
||||
- Unauthorized role, used when access-key is not sent in access-key only
|
||||
mode or "Authorization" header is absent in JWT mode.
|
||||
Example: ``anonymous``. Now whenever "Authorization" header is
|
||||
absent, request's role will default to "anonymous".
|
||||
|
||||
-s, --stripes Number of stripes (default: 1)
|
||||
* - ``--cors-domain <DOMAINS>``
|
||||
- ``HASURA_GRAPHQL_CORS_DOMAIN``
|
||||
- CSV of list of domains, excluding scheme (http/https) and including port,
|
||||
to allow CORS for. Wildcard domains are allowed.
|
||||
|
||||
-c, --connections Number of connections that need to be opened to Postgres
|
||||
(default: 50)
|
||||
* - ``--disable-cors``
|
||||
- N/A
|
||||
- Disable CORS. Do not send any CORS headers on any request.
|
||||
|
||||
--timeout Each connection's idle time before it is closed
|
||||
(default: 180 sec)
|
||||
* - ``--enable-telemetry <true|false>``
|
||||
- ``HASURA_GRAPHQL_ENABLE_TELEMETRY``
|
||||
- Enable anonymous telemetry (default: true)
|
||||
|
||||
-i, --tx-iso Transaction isolation. read-commited / repeatable-read /
|
||||
serializable
|
||||
* - N/A
|
||||
- ``HASURA_GRAPHQL_EVENTS_HTTP_POOL_SIZE``
|
||||
- Max event threads
|
||||
|
||||
--enable-console Enable API console. It is served at '/' and '/console'
|
||||
* - N/A
|
||||
- ``HASURA_GRAPHQL_EVENTS_FETCH_INTERVAL``
|
||||
- Postgres events polling interval
|
||||
|
||||
--use-prepared-statements Use prepared statements for SQL queries (default: true)
|
||||
* - ``-s, --stripes <NO_OF_STRIPES>``
|
||||
- ``HASURA_GRAPHQL_PG_STRIPES``
|
||||
- Number of conns that need to be opened to Postgres (default: 1)
|
||||
|
||||
--enable-telemetry Enable anonymous telemetry (default: true)
|
||||
* - ``-c, --connections <NO_OF_CONNS>``
|
||||
- ``HASURA_GRAPHQL_PG_CONNECTIONS``
|
||||
- Number of conns that need to be opened to Postgres (default: 50)
|
||||
|
||||
* - ``--timeout <SECONDS>``
|
||||
- ``HASURA_GRAPHQL_PG_TIMEOUT``
|
||||
- Each connection's idle time before it is closed (default: 180 sec)
|
||||
|
||||
Default environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* - ``--use-prepared-statements <true|false>``
|
||||
- ``HASURA_GRAPHQL_USE_PREPARED_STATEMENTS``
|
||||
- Use prepared statements for queries (default: true)
|
||||
|
||||
* - ``-i, --tx-iso <TXISO>``
|
||||
- ``HASURA_GRAPHQL_TX_ISOLATION``
|
||||
- transaction isolation. read-committed / repeatable-read / serializable (default: read-commited)
|
||||
|
||||
You can use environment variables to configure defaults instead of using flags:
|
||||
|
||||
.. note::
|
||||
When the equivalent flags for environment variables are used, the flags will take precedence.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ HASURA_GRAPHQL_DATABASE_URL=postgres://user:pass@host:5432/dbname graphql-engine serve
|
||||
|
||||
|
||||
These are the environment variables which are available:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
HASURA_GRAPHQL_DATABASE_URL Postgres database URL
|
||||
<postgres/postgresql>://<user>:<password>@<host>:
|
||||
<port>/<db-name> Example:
|
||||
postgres://admin:mypass@mydomain.com:5432/mydb
|
||||
|
||||
HASURA_GRAPHQL_PG_STRIPES Number of stripes (default: 1)
|
||||
|
||||
HASURA_GRAPHQL_PG_CONNECTIONS Number of connections that need to be opened to
|
||||
Postgres (default: 50)
|
||||
|
||||
HASURA_GRAPHQL_PG_TIMEOUT Each connection's idle time before it is closed
|
||||
(default: 180 sec)
|
||||
|
||||
HASURA_GRAPHQL_TX_ISOLATION transaction isolation. read-committed /
|
||||
repeatable-read / serializable
|
||||
(default: read-commited)
|
||||
HASURA_GRAPHQL_SERVER_HOST IP address of network interface that graphql-engine will listen on
|
||||
|
||||
HASURA_GRAPHQL_SERVER_PORT Port on which graphql-engine should be served
|
||||
|
||||
HASURA_GRAPHQL_ACCESS_KEY Secret access key, required to access this
|
||||
instance. If specified client needs to send
|
||||
'X-Hasura-Access-Key' header
|
||||
|
||||
HASURA_GRAPHQL_AUTH_HOOK The authentication webhook, required to
|
||||
authenticate incoming request
|
||||
|
||||
HASURA_GRAPHQL_AUTH_HOOK_MODE The authentication webhook mode, GET|POST
|
||||
(default: GET)
|
||||
|
||||
HASURA_GRAPHQL_CORS_DOMAIN The domain, including sheme and port,
|
||||
to allow CORS for
|
||||
|
||||
HASURA_GRAPHQL_JWT_SECRET The JSON containing type and the JWK used for
|
||||
verifying. e.g: `{"type": "HS256", "key":
|
||||
"<your-hmac-shared-secret>"}`,`{"type": "RS256",
|
||||
"key": "<your-PEM-RSA-public-key>"}
|
||||
Enable JWT mode, the value of which is a JSON
|
||||
|
||||
HASURA_GRAPHQL_UNAUTHORIZED_ROLE Unauthorized role, used when access-key is not sent
|
||||
in access-key only mode or "Authorization" header
|
||||
is absent in JWT mode
|
||||
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE Enable API console. It is served at
|
||||
'/' and '/console'
|
||||
|
||||
HASURA_GRAPHQL_ENABLE_TELEMETRY Enable anonymous telemetry (default: true)
|
||||
|
||||
HASURA_GRAPHQL_USE_PREPARED_STATEMENTS Use prepared statements for SQL queries
|
||||
(default: true)
|
||||
When the equivalent flags for environment variables are used, the flags will take precedence.
|
@ -146,6 +146,7 @@ library
|
||||
, Hasura.Server.Logging
|
||||
, Hasura.Server.Query
|
||||
, Hasura.Server.Utils
|
||||
, Hasura.Server.Cors
|
||||
, Hasura.Server.Version
|
||||
, Hasura.Server.CheckUpdates
|
||||
, Hasura.Server.Telemetry
|
||||
|
@ -43,10 +43,10 @@ import Hasura.RQL.DML.QueryTemplate
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.Server.Auth (AuthMode (..),
|
||||
getUserInfo)
|
||||
import Hasura.Server.Cors
|
||||
import Hasura.Server.Init
|
||||
import Hasura.Server.Logging
|
||||
import Hasura.Server.Middleware (corsMiddleware,
|
||||
mkDefaultCorsPolicy)
|
||||
import Hasura.Server.Middleware (corsMiddleware)
|
||||
import Hasura.Server.Query
|
||||
import Hasura.Server.Utils
|
||||
import Hasura.Server.Version
|
||||
@ -314,8 +314,8 @@ mkWaiApp isoLevel loggerCtx pool httpManager mode corsCfg enableConsole enableTe
|
||||
httpApp :: CorsConfig -> ServerCtx -> Bool -> Bool -> SpockT IO ()
|
||||
httpApp corsCfg serverCtx enableConsole enableTelemetry = do
|
||||
-- cors middleware
|
||||
unless (ccDisabled corsCfg) $
|
||||
middleware $ corsMiddleware (mkDefaultCorsPolicy $ ccDomain corsCfg)
|
||||
unless (isCorsDisabled corsCfg) $
|
||||
middleware $ corsMiddleware (mkDefaultCorsPolicy corsCfg)
|
||||
|
||||
-- API Console and Root Dir
|
||||
when enableConsole serveApiConsole
|
||||
|
@ -12,6 +12,7 @@ import Data.ASN1.Types (ASN1 (End, IntVal, Start),
|
||||
import Data.Int (Int64)
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.Server.Utils (fmapL)
|
||||
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import qualified Data.PEM as PEM
|
||||
@ -97,10 +98,6 @@ pubKeyToJwk pubKey = do
|
||||
RSAKeyParameters (Base64Integer n) (Base64Integer e) Nothing
|
||||
|
||||
|
||||
fmapL :: (a -> a') -> Either a b -> Either a' b
|
||||
fmapL fn (Left e) = Left (fn e)
|
||||
fmapL _ (Right x) = pure x
|
||||
|
||||
getAtleastOne :: Text -> [a] -> Either Text a
|
||||
getAtleastOne err [] = Left err
|
||||
getAtleastOne _ (x:_) = Right x
|
||||
|
153
server/src-lib/Hasura/Server/Cors.hs
Normal file
153
server/src-lib/Hasura/Server/Cors.hs
Normal file
@ -0,0 +1,153 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
|
||||
-- | CORS (Cross Origin Resource Sharing) related configuration
|
||||
|
||||
module Hasura.Server.Cors
|
||||
( CorsConfig (..)
|
||||
, CorsPolicy (..)
|
||||
, parseOrigin
|
||||
, readCorsDomains
|
||||
, mkDefaultCorsPolicy
|
||||
, isCorsDisabled
|
||||
, Domains (..)
|
||||
) where
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.Server.Utils (fmapL)
|
||||
|
||||
import Control.Applicative (optional)
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Casing as J
|
||||
import qualified Data.Aeson.TH as J
|
||||
import qualified Data.Attoparsec.Text as AT
|
||||
import qualified Data.HashSet as Set
|
||||
import qualified Data.Text as T
|
||||
|
||||
|
||||
data DomainParts =
|
||||
DomainParts
|
||||
{ wdScheme :: !Text
|
||||
, wdHost :: !Text -- the hostname part (without the *.)
|
||||
, wdPort :: !(Maybe Int)
|
||||
} deriving (Show, Eq, Generic, Hashable)
|
||||
|
||||
$(J.deriveToJSON (J.aesonDrop 2 J.snakeCase) ''DomainParts)
|
||||
|
||||
data Domains
|
||||
= Domains
|
||||
{ dmFqdns :: !(Set.HashSet Text)
|
||||
, dmWildcards :: !(Set.HashSet DomainParts)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
$(J.deriveToJSON (J.aesonDrop 2 J.snakeCase) ''Domains)
|
||||
|
||||
data CorsConfig
|
||||
= CCAllowAll
|
||||
| CCAllowedOrigins Domains
|
||||
| CCDisabled
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance J.ToJSON CorsConfig where
|
||||
toJSON c = case c of
|
||||
CCDisabled -> toJ True J.Null
|
||||
CCAllowAll -> toJ False (J.String "*")
|
||||
CCAllowedOrigins d -> toJ False $ J.toJSON d
|
||||
where
|
||||
toJ dis origs =
|
||||
J.object [ "disabled" J..= dis
|
||||
, "allowed_origins" J..= origs
|
||||
]
|
||||
|
||||
isCorsDisabled :: CorsConfig -> Bool
|
||||
isCorsDisabled = \case
|
||||
CCDisabled -> True
|
||||
_ -> False
|
||||
|
||||
readCorsDomains :: String -> Either String CorsConfig
|
||||
readCorsDomains str
|
||||
| str == "*" = pure CCAllowAll
|
||||
| otherwise = do
|
||||
let domains = map T.strip $ T.splitOn "," (T.pack str)
|
||||
pDomains <- mapM parseOptWildcardDomain domains
|
||||
let (fqdns, wcs) = (lefts pDomains, rights pDomains)
|
||||
return $ CCAllowedOrigins $ Domains (Set.fromList fqdns) (Set.fromList wcs)
|
||||
|
||||
|
||||
data CorsPolicy
|
||||
= CorsPolicy
|
||||
{ cpConfig :: !CorsConfig
|
||||
, cpMethods :: ![Text]
|
||||
, cpMaxAge :: !Int
|
||||
} deriving (Show, Eq)
|
||||
|
||||
mkDefaultCorsPolicy :: CorsConfig -> CorsPolicy
|
||||
mkDefaultCorsPolicy cfg =
|
||||
CorsPolicy
|
||||
{ cpConfig = cfg
|
||||
, cpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
||||
, cpMaxAge = 1728000
|
||||
}
|
||||
|
||||
-- | Parsers for wildcard domains
|
||||
|
||||
runParser :: AT.Parser a -> Text -> Either String a
|
||||
runParser = AT.parseOnly
|
||||
|
||||
parseOrigin :: Text -> Either String DomainParts
|
||||
parseOrigin = runParser originParser
|
||||
|
||||
originParser :: AT.Parser DomainParts
|
||||
originParser =
|
||||
domainParser (Just ignoreSubdomain)
|
||||
where ignoreSubdomain = do
|
||||
s <- AT.takeTill (== '.')
|
||||
void $ AT.char '.'
|
||||
return s
|
||||
|
||||
parseOptWildcardDomain :: Text -> Either String (Either Text DomainParts)
|
||||
parseOptWildcardDomain d =
|
||||
fmapL (const errMsg) $ runParser optWildcardDomainParser d
|
||||
where
|
||||
|
||||
optWildcardDomainParser :: AT.Parser (Either Text DomainParts)
|
||||
optWildcardDomainParser =
|
||||
Right <$> wildcardDomainParser <|> Left <$> fqdnParser
|
||||
|
||||
errMsg = "invalid domain: '" <> T.unpack d <> "'. " <> helpMsg
|
||||
helpMsg = "All domains should have scheme + (optional wildcard) host + "
|
||||
<> "(optional port)"
|
||||
|
||||
wildcardDomainParser :: AT.Parser DomainParts
|
||||
wildcardDomainParser = domainParser $ Just (AT.string "*" *> AT.string ".")
|
||||
|
||||
fqdnParser :: AT.Parser Text
|
||||
fqdnParser = do
|
||||
(DomainParts scheme host port) <- domainParser Nothing
|
||||
let sPort = maybe "" (\p -> ":" <> T.pack (show p)) port
|
||||
return $ scheme <> host <> sPort
|
||||
|
||||
|
||||
domainParser :: Maybe (AT.Parser Text) -> AT.Parser DomainParts
|
||||
domainParser parser = do
|
||||
scheme <- schemeParser
|
||||
forM_ parser void
|
||||
host <- hostPortParser
|
||||
port <- optional portParser
|
||||
return $ DomainParts scheme host port
|
||||
|
||||
where
|
||||
schemeParser :: AT.Parser Text
|
||||
schemeParser = AT.string "http://" <|> AT.string "https://"
|
||||
|
||||
hostPortParser :: AT.Parser Text
|
||||
hostPortParser = hostWithPortParser <|> AT.takeText
|
||||
|
||||
hostWithPortParser :: AT.Parser Text
|
||||
hostWithPortParser = do
|
||||
h <- AT.takeWhile1 (/= ':')
|
||||
void $ AT.char ':'
|
||||
return h
|
||||
|
||||
portParser :: AT.Parser Int
|
||||
portParser = AT.decimal
|
@ -8,14 +8,17 @@ import System.Exit (exitFailure)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.String as DataString
|
||||
import qualified Data.Text as T
|
||||
import qualified Hasura.Logging as L
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as PP
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types (RoleName (..))
|
||||
import Hasura.Server.Auth
|
||||
import Hasura.Server.Cors
|
||||
import Hasura.Server.Logging
|
||||
import Hasura.Server.Utils
|
||||
import Network.Wai.Handler.Warp
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as PP
|
||||
|
||||
import qualified Hasura.Logging as L
|
||||
|
||||
|
||||
initErrExit :: (Show e) => e -> IO a
|
||||
@ -41,20 +44,11 @@ data RawServeOptions
|
||||
, rsoAuthHook :: !RawAuthHook
|
||||
, rsoJwtSecret :: !(Maybe Text)
|
||||
, rsoUnAuthRole :: !(Maybe RoleName)
|
||||
, rsoCorsConfig :: !RawCorsConfig
|
||||
, rsoCorsConfig :: !(Maybe CorsConfig)
|
||||
, rsoEnableConsole :: !Bool
|
||||
, rsoEnableTelemetry :: !(Maybe Bool)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data CorsConfigG a
|
||||
= CorsConfigG
|
||||
{ ccDomain :: !a
|
||||
, ccDisabled :: !Bool
|
||||
} deriving (Show, Eq)
|
||||
|
||||
type RawCorsConfig = CorsConfigG (Maybe T.Text)
|
||||
type CorsConfig = CorsConfigG T.Text
|
||||
|
||||
data ServeOptions
|
||||
= ServeOptions
|
||||
{ soPort :: !Int
|
||||
@ -133,6 +127,9 @@ instance FromEnv Bool where
|
||||
instance FromEnv Q.TxIsolation where
|
||||
fromEnv = readIsoLevel
|
||||
|
||||
instance FromEnv CorsConfig where
|
||||
fromEnv = readCorsDomains
|
||||
|
||||
parseStrAsBool :: String -> Either String Bool
|
||||
parseStrAsBool t
|
||||
| t `elem` truthVals = Right True
|
||||
@ -244,9 +241,8 @@ mkServeOptions rso = do
|
||||
authHookTyEnv mType = fromMaybe AHTGet <$>
|
||||
withEnv mType "HASURA_GRAPHQL_AUTH_HOOK_TYPE"
|
||||
|
||||
mkCorsConfig (CorsConfigG mDom isDis) = do
|
||||
domEnv <- fromMaybe "*" <$> withEnv mDom (fst corsDomainEnv)
|
||||
return $ CorsConfigG domEnv isDis
|
||||
mkCorsConfig mCfg =
|
||||
fromMaybe CCAllowAll <$> withEnv mCfg (fst corsDomainEnv)
|
||||
|
||||
mkExamplesDoc :: [[String]] -> PP.Doc
|
||||
mkExamplesDoc exampleLines =
|
||||
@ -308,6 +304,9 @@ serveCmdFooter =
|
||||
, [ "# Start GraphQL Engine with restrictive CORS policy (only allow https://example.com:8080)"
|
||||
, "graphql-engine --database-url <database-url> serve --cors-domain https://example.com:8080"
|
||||
]
|
||||
, [ "# Start GraphQL Engine with multiple domains for CORS (https://example.com, http://localhost:3000 and https://*.foo.bar.com)"
|
||||
, "graphql-engine --database-url <database-url> serve --cors-domain \"https://example.com, https://*.foo.bar.com, http://localhost:3000\""
|
||||
]
|
||||
, [ "# Start GraphQL Engine with Authentication Webhook (GET)"
|
||||
, "graphql-engine --database-url <database-url> serve --access-key <secretaccesskey>"
|
||||
<> " --auth-hook https://mywebhook.com/get"
|
||||
@ -324,7 +323,7 @@ serveCmdFooter =
|
||||
envVarDoc = mkEnvVarDoc $ envVars <> eventEnvs
|
||||
envVars =
|
||||
[ servePortEnv, serveHostEnv, pgStripesEnv, pgConnsEnv, pgTimeoutEnv
|
||||
, txIsoEnv, accessKeyEnv, authHookEnv , authHookModeEnv
|
||||
, pgUsePrepareEnv, txIsoEnv, accessKeyEnv, authHookEnv , authHookModeEnv
|
||||
, jwtSecretEnv , unAuthRoleEnv, corsDomainEnv , enableConsoleEnv
|
||||
, enableTelemetryEnv
|
||||
]
|
||||
@ -370,7 +369,7 @@ pgTimeoutEnv =
|
||||
pgUsePrepareEnv :: (String, String)
|
||||
pgUsePrepareEnv =
|
||||
( "HASURA_GRAPHQL_USE_PREPARED_STATEMENTS"
|
||||
, "Use prepared statements for queries (default: True)"
|
||||
, "Use prepared statements for queries (default: true)"
|
||||
)
|
||||
|
||||
txIsoEnv :: (String, String)
|
||||
@ -388,13 +387,13 @@ accessKeyEnv =
|
||||
authHookEnv :: (String, String)
|
||||
authHookEnv =
|
||||
( "HASURA_GRAPHQL_AUTH_HOOK"
|
||||
, "The authentication webhook, required to authenticate requests"
|
||||
, "URL of the authorization webhook required to authorize requests"
|
||||
)
|
||||
|
||||
authHookModeEnv :: (String, String)
|
||||
authHookModeEnv =
|
||||
( "HASURA_GRAPHQL_AUTH_HOOK_MODE"
|
||||
, "The authentication webhook mode (default: GET)"
|
||||
, "HTTP method to use for authorization webhook (default: GET)"
|
||||
)
|
||||
|
||||
jwtSecretEnv :: (String, String)
|
||||
@ -413,7 +412,8 @@ unAuthRoleEnv =
|
||||
corsDomainEnv :: (String, String)
|
||||
corsDomainEnv =
|
||||
( "HASURA_GRAPHQL_CORS_DOMAIN"
|
||||
, "The domain, including scheme and port, to allow CORS for"
|
||||
, "CSV of list of domains, excluding scheme (http/https) and including port, "
|
||||
++ "to allow CORS for. Wildcard domains are allowed. See docs for details."
|
||||
)
|
||||
|
||||
enableConsoleEnv :: (String, String)
|
||||
@ -436,24 +436,24 @@ parseRawConnInfo =
|
||||
where
|
||||
host = optional $
|
||||
strOption ( long "host" <>
|
||||
metavar "HOST" <>
|
||||
metavar "<HOST>" <>
|
||||
help "Postgres server host" )
|
||||
|
||||
port = optional $
|
||||
option auto ( long "port" <>
|
||||
short 'p' <>
|
||||
metavar "PORT" <>
|
||||
metavar "<PORT>" <>
|
||||
help "Postgres server port" )
|
||||
|
||||
user = optional $
|
||||
strOption ( long "user" <>
|
||||
short 'u' <>
|
||||
metavar "USER" <>
|
||||
metavar "<USER>" <>
|
||||
help "Database user name" )
|
||||
|
||||
password =
|
||||
strOption ( long "password" <>
|
||||
metavar "PASSWORD" <>
|
||||
metavar "<PASSWORD>" <>
|
||||
value "" <>
|
||||
help "Password of the user"
|
||||
)
|
||||
@ -461,14 +461,14 @@ parseRawConnInfo =
|
||||
dbUrl = optional $
|
||||
strOption
|
||||
( long "database-url" <>
|
||||
metavar "DATABASE-URL" <>
|
||||
metavar "<DATABASE-URL>" <>
|
||||
help (snd databaseUrlEnv)
|
||||
)
|
||||
|
||||
dbName = optional $
|
||||
strOption ( long "dbname" <>
|
||||
short 'd' <>
|
||||
metavar "NAME" <>
|
||||
metavar "<DBNAME>" <>
|
||||
help "Database name to connect to"
|
||||
)
|
||||
|
||||
@ -497,7 +497,7 @@ parseTxIsolation = optional $
|
||||
option (eitherReader readIsoLevel)
|
||||
( long "tx-iso" <>
|
||||
short 'i' <>
|
||||
metavar "TXISO" <>
|
||||
metavar "<TXISO>" <>
|
||||
help (snd txIsoEnv)
|
||||
)
|
||||
|
||||
@ -509,7 +509,7 @@ parseConnParams =
|
||||
option auto
|
||||
( long "stripes" <>
|
||||
short 's' <>
|
||||
metavar "NO OF STRIPES" <>
|
||||
metavar "<NO OF STRIPES>" <>
|
||||
help (snd pgStripesEnv)
|
||||
)
|
||||
|
||||
@ -517,20 +517,20 @@ parseConnParams =
|
||||
option auto
|
||||
( long "connections" <>
|
||||
short 'c' <>
|
||||
metavar "NO OF CONNS" <>
|
||||
metavar "<NO OF CONNS>" <>
|
||||
help (snd pgConnsEnv)
|
||||
)
|
||||
|
||||
timeout = optional $
|
||||
option auto
|
||||
( long "timeout" <>
|
||||
metavar "SECONDS" <>
|
||||
metavar "<SECONDS>" <>
|
||||
help (snd pgTimeoutEnv)
|
||||
)
|
||||
allowPrepare = optional $
|
||||
option (eitherReader parseStrAsBool)
|
||||
( long "use-prepared-statements" <>
|
||||
metavar "USE PREPARED STATEMENTS" <>
|
||||
metavar "<true|false>" <>
|
||||
help (snd pgUsePrepareEnv)
|
||||
)
|
||||
|
||||
@ -538,13 +538,13 @@ parseServerPort :: Parser (Maybe Int)
|
||||
parseServerPort = optional $
|
||||
option auto
|
||||
( long "server-port" <>
|
||||
metavar "PORT" <>
|
||||
metavar "<PORT>" <>
|
||||
help (snd servePortEnv)
|
||||
)
|
||||
|
||||
parseServerHost :: Parser (Maybe HostPreference)
|
||||
parseServerHost = optional $ strOption ( long "server-host" <>
|
||||
metavar "HOST" <>
|
||||
metavar "<HOST>" <>
|
||||
help "Host on which graphql-engine will listen (default: *)"
|
||||
)
|
||||
|
||||
@ -552,7 +552,7 @@ parseAccessKey :: Parser (Maybe AccessKey)
|
||||
parseAccessKey =
|
||||
optional $ AccessKey <$>
|
||||
strOption ( long "access-key" <>
|
||||
metavar "SECRET ACCESS KEY" <>
|
||||
metavar "<SECRET ACCESS KEY>" <>
|
||||
help (snd accessKeyEnv)
|
||||
)
|
||||
|
||||
@ -569,13 +569,13 @@ parseWebHook =
|
||||
where
|
||||
url = optional $
|
||||
strOption ( long "auth-hook" <>
|
||||
metavar "AUTHENTICATION WEB HOOK" <>
|
||||
metavar "<WEB HOOK URL>" <>
|
||||
help (snd authHookEnv)
|
||||
)
|
||||
urlType = optional $
|
||||
option (eitherReader readHookType)
|
||||
( long "auth-hook-mode" <>
|
||||
metavar "GET|POST" <>
|
||||
metavar "<GET|POST>" <>
|
||||
help (snd authHookModeEnv)
|
||||
)
|
||||
|
||||
@ -584,7 +584,7 @@ parseJwtSecret :: Parser (Maybe Text)
|
||||
parseJwtSecret =
|
||||
optional $ strOption
|
||||
( long "jwt-secret" <>
|
||||
metavar "JWK" <>
|
||||
metavar "<JSON CONFIG>" <>
|
||||
help (snd jwtSecretEnv)
|
||||
)
|
||||
|
||||
@ -596,26 +596,28 @@ jwtSecretHelp = "The JSON containing type and the JWK used for verifying. e.g: "
|
||||
parseUnAuthRole :: Parser (Maybe RoleName)
|
||||
parseUnAuthRole = optional $
|
||||
RoleName <$> strOption ( long "unauthorized-role" <>
|
||||
metavar "UNAUTHORIZED ROLE" <>
|
||||
metavar "<ROLE>" <>
|
||||
help (snd unAuthRoleEnv)
|
||||
)
|
||||
|
||||
parseCorsConfig :: Parser RawCorsConfig
|
||||
parseCorsConfig =
|
||||
CorsConfigG <$> corsDomain <*> disableCors
|
||||
parseCorsConfig :: Parser (Maybe CorsConfig)
|
||||
parseCorsConfig = mapCC <$> disableCors <*> corsDomain
|
||||
where
|
||||
corsDomain =
|
||||
optional (strOption
|
||||
( long "cors-domain" <>
|
||||
metavar "CORS DOMAIN" <>
|
||||
help (snd corsDomainEnv)
|
||||
)
|
||||
)
|
||||
corsDomain = optional $
|
||||
option (eitherReader readCorsDomains)
|
||||
( long "cors-domain" <>
|
||||
metavar "<DOMAINS>" <>
|
||||
help (snd corsDomainEnv)
|
||||
)
|
||||
|
||||
disableCors =
|
||||
switch ( long "disable-cors" <>
|
||||
help "Disable CORS handling"
|
||||
help "Disable CORS. Do not send any CORS headers on any request"
|
||||
)
|
||||
|
||||
mapCC isDisabled domains =
|
||||
bool domains (Just CCDisabled) isDisabled
|
||||
|
||||
parseEnableConsole :: Parser Bool
|
||||
parseEnableConsole =
|
||||
switch ( long "enable-console" <>
|
||||
@ -649,8 +651,7 @@ serveOptsToLog so =
|
||||
, "auth_hook" J..= (ahUrl <$> soAuthHook so)
|
||||
, "auth_hook_mode" J..= (show . ahType <$> soAuthHook so)
|
||||
, "unauth_role" J..= soUnAuthRole so
|
||||
, "cors_domain" J..= (ccDomain . soCorsConfig) so
|
||||
, "cors_disabled" J..= (ccDisabled . soCorsConfig) so
|
||||
, "cors_config" J..= soCorsConfig so
|
||||
, "enable_console" J..= soEnableConsole so
|
||||
, "enable_telemetry" J..= soEnableTelemetry so
|
||||
, "use_prepared_statements" J..= (Q.cpAllowPrepare . soConnParams) so
|
||||
|
@ -3,41 +3,40 @@ module Hasura.Server.Middleware where
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Network.Wai
|
||||
|
||||
import Control.Applicative
|
||||
import Hasura.Prelude
|
||||
import Hasura.Server.Cors
|
||||
import Hasura.Server.Logging (getRequestHeader)
|
||||
import Hasura.Server.Utils
|
||||
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.HashSet as Set
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import qualified Network.HTTP.Types as H
|
||||
|
||||
data CorsPolicy
|
||||
= CorsPolicy
|
||||
{ cpDomain :: !T.Text
|
||||
, cpMethods :: ![T.Text]
|
||||
, cpMaxAge :: !Int
|
||||
} deriving (Show, Eq)
|
||||
|
||||
mkDefaultCorsPolicy :: T.Text -> CorsPolicy
|
||||
mkDefaultCorsPolicy domain =
|
||||
CorsPolicy
|
||||
{ cpDomain = domain
|
||||
, cpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
||||
, cpMaxAge = 1728000
|
||||
}
|
||||
|
||||
corsMiddleware :: CorsPolicy -> Middleware
|
||||
corsMiddleware policy app req sendResp =
|
||||
maybe (app req sendResp) handleCors $ getRequestHeader "Origin" req
|
||||
|
||||
where
|
||||
handleCors origin
|
||||
| cpDomain policy /= "*" && origin /= TE.encodeUtf8 (cpDomain policy) = app req sendResp
|
||||
| otherwise =
|
||||
case requestMethod req of
|
||||
"OPTIONS" -> sendResp $ respondPreFlight origin
|
||||
_ -> app req $ sendResp . injectCorsHeaders origin
|
||||
handleCors origin = case cpConfig policy of
|
||||
CCDisabled -> app req sendResp
|
||||
CCAllowAll -> sendCors origin
|
||||
CCAllowedOrigins ds
|
||||
-- if the origin is in our cors domains, send cors headers
|
||||
| bsToTxt origin `elem` dmFqdns ds -> sendCors origin
|
||||
-- if current origin is part of wildcard domain list, send cors
|
||||
| inWildcardList ds (bsToTxt origin) -> sendCors origin
|
||||
-- otherwise don't send cors headers
|
||||
| otherwise -> app req sendResp
|
||||
|
||||
sendCors :: B.ByteString -> IO ResponseReceived
|
||||
sendCors origin =
|
||||
case requestMethod req of
|
||||
"OPTIONS" -> sendResp $ respondPreFlight origin
|
||||
_ -> app req $ sendResp . injectCorsHeaders origin
|
||||
|
||||
respondPreFlight :: B.ByteString -> Response
|
||||
respondPreFlight origin =
|
||||
@ -67,3 +66,7 @@ corsMiddleware policy app req sendResp =
|
||||
|
||||
setHeaders hdrs = mapResponseHeaders (\h -> mkRespHdrs hdrs ++ h)
|
||||
mkRespHdrs = map (\(k,v) -> (CI.mk k, v))
|
||||
|
||||
inWildcardList :: Domains -> Text -> Bool
|
||||
inWildcardList (Domains _ wildcards) origin =
|
||||
either (const False) (`Set.member` wildcards) $ parseOrigin origin
|
||||
|
@ -126,3 +126,8 @@ matchRegex regex caseSensitive src =
|
||||
}
|
||||
execOption = TDFA.defaultExecOpt {TDFA.captureGroups = False}
|
||||
compiledRegexE = TDFA.compile compOpt execOption regex
|
||||
|
||||
|
||||
fmapL :: (a -> a') -> Either a b -> Either a' b
|
||||
fmapL fn (Left e) = Left (fn e)
|
||||
fmapL _ (Right x) = pure x
|
||||
|
@ -27,6 +27,12 @@ def pytest_addoption(parser):
|
||||
"--hge-jwt-conf", metavar="HGE_JWT_CONF", help="The JWT conf", required=False
|
||||
)
|
||||
|
||||
parser.addoption(
|
||||
"--test-cors", action="store_true",
|
||||
required=False,
|
||||
help="Run testcases for CORS configuration"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def hge_ctx(request):
|
||||
@ -38,6 +44,7 @@ def hge_ctx(request):
|
||||
webhook_insecure = request.config.getoption('--test-webhook-insecure')
|
||||
hge_jwt_key_file = request.config.getoption('--hge-jwt-key-file')
|
||||
hge_jwt_conf = request.config.getoption('--hge-jwt-conf')
|
||||
test_cors = request.config.getoption('--test-cors')
|
||||
try:
|
||||
hge_ctx = HGECtx(
|
||||
hge_url=hge_url,
|
||||
@ -50,6 +57,7 @@ def hge_ctx(request):
|
||||
)
|
||||
except HGECtxError as e:
|
||||
pytest.exit(str(e))
|
||||
|
||||
yield hge_ctx # provide the fixture value
|
||||
print("teardown hge_ctx")
|
||||
hge_ctx.teardown()
|
||||
|
54
server/tests-py/test_cors.py
Normal file
54
server/tests-py/test_cors.py
Normal file
@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
|
||||
if not pytest.config.getoption("--test-cors"):
|
||||
pytest.skip("--test-cors flag is missing, skipping tests", allow_module_level=True)
|
||||
|
||||
|
||||
def url(hge_ctx):
|
||||
return hge_ctx.hge_url + '/v1/version'
|
||||
|
||||
class TestCors():
|
||||
"""
|
||||
currently assumes the following is set:
|
||||
HASURA_GRAPHQL_CORS_DOMAIN="http://*.localhost, http://localhost:3000, https://*.foo.bar.com"
|
||||
"""
|
||||
def assert_cors_headers(self, origin, resp):
|
||||
headers = resp.headers
|
||||
assert 'Access-Control-Allow-Origin' in headers
|
||||
assert headers['Access-Control-Allow-Origin'] == origin
|
||||
assert 'Access-Control-Allow-Credentials' in headers
|
||||
assert headers['Access-Control-Allow-Credentials'] == 'true'
|
||||
assert 'Access-Control-Allow-Methods' in headers
|
||||
assert headers['Access-Control-Allow-Methods'] == 'GET,POST,PUT,PATCH,DELETE,OPTIONS'
|
||||
|
||||
def test_cors_foo_bar_top_domain(self, hge_ctx):
|
||||
origin = 'https://foo.bar.com'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
with pytest.raises(AssertionError):
|
||||
self.assert_cors_headers(origin, resp)
|
||||
|
||||
def test_cors_foo_bar_sub_domain(self, hge_ctx):
|
||||
origin = 'https://app.foo.bar.com'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
self.assert_cors_headers(origin, resp)
|
||||
|
||||
def test_cors_foo_bar_sub_sub_domain_fails(self, hge_ctx):
|
||||
origin = 'https://inst1.app.foo.bar.com'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
with pytest.raises(AssertionError):
|
||||
self.assert_cors_headers(origin, resp)
|
||||
|
||||
def test_cors_localhost_domain_w_port(self, hge_ctx):
|
||||
origin = 'http://localhost:3000'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
self.assert_cors_headers(origin, resp)
|
||||
|
||||
def test_cors_localhost_domain(self, hge_ctx):
|
||||
origin = 'http://app.localhost'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
self.assert_cors_headers(origin, resp)
|
||||
|
||||
def test_cors_wrong_domain(self, hge_ctx):
|
||||
origin = 'https://example.com'
|
||||
resp = hge_ctx.http.get(url(hge_ctx), headers={'Origin': origin})
|
||||
assert 'Access-Control-Allow-Origin' not in resp.headers
|
Loading…
Reference in New Issue
Block a user