diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4653af7..b52223031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Embed associations, e.g. `/film?select=*,director(*)` - @ruslantalpa - Filter columns, e.g. `?select=col1,col2` - @ruslantalpa - Does not execute the count total if header "Prefer: count=none" - @diogob +- Postgres connection string argument - @calebmer ### Removed - API versioning feature - @calebmer +- `--db-x` command line arguments - @calebmer ### Fixed - Tolerate a missing role in user creation - @calebmer diff --git a/README.md b/README.md index 4cd691b14..121c4f38d 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,16 @@ your own projects. Download the binary ([latest release](https://github.com/begriffs/postgrest/releases/latest)) and invoke like so: ```bash -postgrest --db-host localhost --db-port 5432 \ - --db-name my_db --db-user postgres \ - --db-pass foobar --db-pool 200 \ - --anonymous postgres --port 3000 \ - --schema public +postgrest postgres://postgres:foobar@localhost:5432/my_db \ + --port 3000 \ + --schema public \ + --anonymous postgres \ + --pool 200 ``` +For more information on valid connection strings see the +[Postgres docs](http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING). + In production include the `--secure` option which redirects all requests to HTTPS. Note that PostgREST does not handle the SSL internally and must be put behind another server that does (such diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 0729f337c..916098229 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -57,8 +57,8 @@ import PostgREST.Types import Prelude -app :: DbStructure -> AppConfig -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response -app dbstructure conf reqBody dbrole req = +app :: DbStructure -> AppConfig -> Text -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response +app dbstructure conf authenticator reqBody dbrole req = case (path, verb) of ([], _) -> do @@ -295,7 +295,6 @@ app dbstructure conf reqBody dbrole req = hasPrefer val = any (\(h,v) -> h == "Prefer" && v == val) hdrs accept = lookupHeader hAccept schema = cs $ configSchema conf - authenticator = cs $ configDbUser conf jwtSecret = cs $ configJwtSecret conf range = rangeRequested hdrs allOrigins = ("Access-Control-Allow-Origin", "*") :: Header diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 03fdd05b2..b83768da8 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -34,34 +34,25 @@ import Prelude -- | Data type to store all command line options data AppConfig = AppConfig { - configDbName :: String - , configDbPort :: Int - , configDbUser :: String - , configDbPass :: String - , configDbHost :: String - + configDatabase :: String , configPort :: Int , configAnonRole :: String - , configSecure :: Bool - , configPool :: Int , configSchema :: String + , configSecure :: Bool , configJwtSecret :: String + , configPool :: Int } argParser :: Parser AppConfig argParser = AppConfig - <$> strOption (long "db-name" <> short 'd' <> metavar "NAME" <> help "name of database") - <*> option auto (long "db-port" <> short 'P' <> metavar "PORT" <> value 5432 <> help "postgres server port" <> showDefault) - <*> strOption (long "db-user" <> short 'U' <> metavar "ROLE" <> help "postgres authenticator role") - <*> strOption (long "db-pass" <> metavar "PASS" <> value "" <> help "password for authenticator role") - <*> strOption (long "db-host" <> metavar "HOST" <> value "localhost" <> help "postgres server hostname" <> showDefault) + <$> argument str (help "database connection string" <> metavar "STRING") - <*> option auto (long "port" <> short 'p' <> metavar "PORT" <> value 3000 <> help "port number on which to run HTTP server" <> showDefault) - <*> strOption (long "anonymous" <> short 'a' <> metavar "ROLE" <> help "postgres role to use for non-authenticated requests") - <*> switch (long "secure" <> short 's' <> help "Redirect all requests to HTTPS") - <*> option auto (long "db-pool" <> metavar "COUNT" <> value 10 <> help "Max connections in database pool" <> showDefault) - <*> strOption (long "schema" <> short 'S' <> metavar "NAME" <> value "public" <> help "Schema to use for API routes" <> showDefault) - <*> strOption (long "jwt-secret" <> metavar "SECRET" <> value "secret" <> help "Secret used to encrypt and decrypt JWT tokens)" <> showDefault) + <*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault) + <*> strOption (long "anonymous" <> short 'a' <> help "postgres role to use for non-authenticated requests" <> metavar "ROLE") + <*> strOption (long "schema" <> short 'S' <> help "schema to use for API routes" <> metavar "NAME" <> value "1" <> showDefault) + <*> switch (long "secure" <> short 's' <> help "redirect all requests to HTTPS") + <*> strOption (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET" <> value "secret" <> showDefault) + <*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault) defaultCorsPolicy :: CorsResourcePolicy defaultCorsPolicy = CorsResourcePolicy Nothing diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index 2c1567c51..180bde91a 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -31,7 +31,7 @@ import PostgREST.Config (AppConfig (..), isServerVersionSupported :: H.Session P.Postgres IO Bool isServerVersionSupported = do - Identity (row :: Text) <- H.tx Nothing $ H.singleEx $ [H.stmt|SHOW server_version_num|] + Identity (row :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SHOW server_version_num|] return $ read (cs row) >= minimumPgVersion main :: IO () @@ -50,11 +50,7 @@ main = do Prelude.putStrLn $ "Listening on port " ++ (show $ configPort conf :: String) - let pgSettings = P.ParamSettings (cs $ configDbHost conf) - (fromIntegral $ configDbPort conf) - (cs $ configDbUser conf) - (cs $ configDbPass conf) - (cs $ configDbName conf) + let pgSettings = P.StringSettings $ cs (configDatabase conf) appSettings = setPort port . setServerName (cs $ "postgrest/" <> prettyVersion) $ defaultSettings @@ -71,6 +67,10 @@ main = do fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0" ) supportedOrError + Right authenticator <- H.session pool $ do + Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|] + return role + let txSettings = Just (H.ReadCommitted, Just True) metadata <- H.session pool $ H.tx txSettings $ do tabs <- allTables @@ -89,9 +89,8 @@ main = do , primaryKeys=keys } - runSettings appSettings $ middle $ \ req respond -> do body <- strictRequestBody req resOrError <- liftIO $ H.session pool $ H.tx txSettings $ - authenticated conf (app dbstructure conf body) req + authenticated conf authenticator (app dbstructure conf authenticator body) req either (respond . errResponse) respond resOrError diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index b2dfee16a..e841c592c 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -33,22 +33,21 @@ import PostgREST.Config (AppConfig (..), corsPolicy) import Prelude -authenticated :: forall s. AppConfig -> +authenticated :: forall s. AppConfig -> Text -> (DbRole -> Request -> H.Tx P.Postgres s Response) -> Request -> H.Tx P.Postgres s Response -authenticated conf app req = do +authenticated conf authenticator app req = do attempt <- httpRequesterRole (requestHeaders req) case attempt of MalformedAuth -> return $ responseLBS status400 [] "Malformed basic auth header" LoginFailed -> return $ responseLBS status401 [] "Invalid username or password" - LoginSuccess role uid -> if role /= currentRole then runInRole role uid else app currentRole req - NoCredentials -> if anon /= currentRole then runInRole anon "" else app currentRole req + LoginSuccess role uid -> if role /= authenticator then runInRole role uid else app authenticator req + NoCredentials -> if anon /= authenticator then runInRole anon "" else app authenticator req where jwtSecret = cs $ configJwtSecret conf - currentRole = cs $ configDbUser conf anon = cs $ configAnonRole conf httpRequesterRole :: RequestHeaders -> H.Tx P.Postgres s LoginAttempt httpRequesterRole hdrs = do diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index 1587dabd3..c4b8727cc 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -20,6 +20,7 @@ import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange, import Codec.Binary.Base64.String (encode) import Data.CaseInsensitive (CI(..)) import Data.Maybe (fromMaybe) +import Data.Functor.Identity import Text.Regex.TDFA ((=~)) import qualified Data.ByteString.Char8 as BS import System.Process (readProcess) @@ -33,28 +34,31 @@ import PostgREST.Error(errResponse) import PostgREST.PgStructure import PostgREST.Types +dbString :: String +dbString = "postgres://postgrest_test@localhost:5432/postgrest_test" + isLeft :: Either a b -> Bool isLeft (Left _ ) = True isLeft _ = False cfg :: AppConfig -cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "test" "safe" +cfg = AppConfig dbString 3000 "postgrest_anonymous" "test" False "safe" 10 testPoolOpts :: PoolSettings testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30 pgSettings :: P.Settings -pgSettings = P.ParamSettings (cs $ configDbHost cfg) - (fromIntegral $ configDbPort cfg) - (cs $ configDbUser cfg) - (cs $ configDbPass cfg) - (cs $ configDbName cfg) +pgSettings = P.StringSettings $ cs dbString withApp :: ActionWith Application -> IO () withApp perform = do pool :: H.Pool P.Postgres <- H.acquirePool pgSettings testPoolOpts + Right authenticator <- H.session pool $ do + Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|] + return role + let txSettings = Just (H.ReadCommitted, Just True) metadata <- H.session pool $ H.tx txSettings $ do tabs <- allTables @@ -76,7 +80,7 @@ withApp perform = do perform $ middle $ \req resp -> do body <- strictRequestBody req result <- liftIO $ H.session pool $ H.tx txSettings - $ authenticated cfg (app dbstructure cfg body) req + $ authenticated cfg authenticator (app dbstructure cfg authenticator body) req either (resp . errResponse) resp result where middle = defaultMiddle False