Enable postgres configuration via environment variable for Heroku.

[?]
May 28, 2015, 11:38 PM
V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC

Dependencies

  • [2] 4U7F3CPI THE GREAT RENAMING OF THINGS!
  • [3] 45QJYWN3 Fixing up the README. Still struggling with the ending.
  • [4] 2Y2QZFVF Switch to more modern cabal2nix-based workflow.
  • [5] ADMKQQGC Initial empty Snap project.
  • [6] NJZ3DKZY THEY CAN TALK!
  • [7] TNR3TEHK Switch to Postgres + snaplet arch compiles.
  • [8] Z3M53KTL Adrift.
  • [9] 7XN3I3QJ Add 'loggedIntervals' endpoint.
  • [10] RSEB2NFG Replacing Snap with Scotty.
  • [11] BROSTG5K Beginning of modularization of server.
  • [12] VJPT6HDR Fix remaining type errors after addition of login handler.
  • [13] EYGIUUQZ Restore remainder of endpoints to compiling status.
  • [14] I2KHGVD4 Require project permissions for access to most data.
  • [15] A6HKMINB Attempting to improve JSON handling.
  • [16] GCVQD44V Create amends endpoint, switch to UUID primary keys
  • [17] XTBSG4C7 Adding serveJSON combinator to eliminate some boilerplate from handlers.
  • [18] RPAJLHMT Change to use UUIDs instead of ints for primary keys.
  • [19] W35DDBFY Factor common JSON conversions up into client lib module.
  • [20] 64C6AWH6 Rename Ananke -> Quixotic, project reboot.
  • [21] EMVTF2IW WIP moving back to snap.
  • [22] LAROLAYU WIP
  • [23] 64VI73NP Server now compiles using abstracted SQLite
  • [24] WZUHEZSB Start of migration back toward snap.
  • [25] TCOAKCGG Completed conversion to snap.
  • [26] IZEVQF62 Work in progress replacing sqlite with postgres.
  • [27] O5FVTOM6 Undo JSON silliness, enable a couple more routes.

Change contents

  • edit in notes.md at line 23
    [3.19096]
    (12:36:54 PM) nuttycom1: Reiterating my earlier question, this time via StackOverflow: http://stackoverflow.com/questions/30466275/http-basic-auth-in-snap
    (12:41:03 PM) dmj`: nuttycom1: why not use snap's cookie-based auth?
    (12:41:33 PM) carter_cloud: mightybyte: lpsmith did that posgres-simple snaplet transaction bug ever get fixed?
    (12:41:40 PM) nuttycom1: dmj`: my clients aren't necessarily browsers.
    (12:41:55 PM) nuttycom1: In fact, they're mostly not browsers.
    (12:43:17 PM) dmj`: nuttycom1: are they mobile clients?
    (12:43:31 PM) nuttycom1: dmj`: no, other servers.
    (12:48:21 PM) dmj`: nuttycom1: basic auth isn't secure, you're sending the password (base-64 encoded) on every request, it could be fine if it's all over https. But still, a token based system would be more secure. Are these severs internal / external? (i.e. yours or someone elses)
    (12:53:16 PM) nuttycom1: dmj`: Token-based systems are no more secure if they're not over https, but that's a side point; in my experience it's common in any case to use the Authorization headers to carry authentication tokens so that they don't have to pollute the request payload in other ways.
    (12:54:14 PM) nuttycom1: I have a bunch of CLI tools that access my services via curl and such, which prompt for auth credentials along the way. Using Basic is convenient for these kinds of tools.
    (12:54:49 PM) nuttycom1: All the servers *are* in my control, however. I suppose I could use an X-My-Auth header or some such, but I don't much see the point.
    (12:55:38 PM) nuttycom1: At least, they're under my control at the moment, but obviously the API I'm providing is ultimately going to be accessible to anyone on the open web, so I might as well make things easy for them.
    (12:59:00 PM) mightybyte: carter_cloud: Yes
    (12:59:32 PM) mightybyte: http://blog.melding-monads.com/2015/02/12/announcing-snaplet-postgresql-simple-0-6/
    (01:00:42 PM) dmj`: nuttycom1: token based systems only transmit the credentials on the initialize request to generate the token, not on every request, so not sure how it's just as secure. It seems like setting up your own oauth provider would be ideal in this case, especially if you plan on releasing this api to the public. If your servers are not exposed to the outside world, why even have them authenticate against each other, if you're using A
    (01:00:43 PM) dmj`: you using a VPC?
    (01:00:49 PM) dmj`: initial*
    (01:00:52 PM) mightybyte: carter_cloud: I guess that post never hit reddit.
    (01:02:17 PM) mightybyte: nuttycom1: I don't know of a basic auth package for snap (probably in part due to the concerns dmj` has pointed out), but I wouldn't expect that it would be too hard to write.
    (01:02:42 PM) nuttycom1: mightybyte: Hah.... yeah, I provide one as context in my SO question.
    (01:04:49 PM) carter_cloud: mightybyte: btw Stephen Diehl has a neat prototype of a streaming Postgres binding https://github.com/elsen-trading/pgstream
    (01:07:49 PM) nuttycom1: dmj`: fair enough; I suppose that I can add token handling to the CLI tools, it's just a little more work since I'll have to encrypt the token before it's stored locally, and require the user to decrypt on each request rather than provide credentials. That's probably the right way to go.
    (01:07:51 PM) dmj`: nuttycom1: I can't speak for other token-based systems, but json web tokens are typically encrpyted (HMAC SHA-256), basic auth data is just encoded. So your last line of defense in basic auth is ssl, and your surface area (every request) is much larger. I dunno, if you think basic auth suits your needs then go for it, but it sounds to me like you should become an oauth provider here.
    (01:08:16 PM) mightybyte: nuttycom1: responded
    (01:09:10 PM) nuttycom1: mightybyte: thanks!
    (01:10:45 PM) dmj`: nuttycom1: yea, you can create your own key server! Then create tcp connections from your key server to all other api servers. You should change the key somewhat frequently (This secret key is used to hash all tokens). So your key server can broadcast the new secret to all servers via tcp, then (if you're using haskell and not that node.js single-threaded stuff) you could migrate everyones tokens over to the new key transparen
    (01:10:45 PM) dmj`: if new key has been sent, then rehash with it, and put it in the header).
    (01:11:16 PM) dmj`: only one-thread, I don't know how people do it
    (01:11:20 PM) dmj`: one thread*
    (01:12:18 PM) nuttycom1: Interesting, thanks dmj`. Looks like I have some additional investigation to do.
  • file addition: QConfig.hs (----------)
    [2.2063]
    module Aftok.QConfig where
    import ClassyPrelude
    import qualified Data.ByteString.Char8 as C
    import qualified Data.Configurator as C
    import qualified Data.Configurator.Types as CT
    import System.Environment
    import System.IO(FilePath)
    import Snap.Core
    import Snap.Snaplet.PostgresqlSimple
    import qualified Snap.Http.Server.Config as SC
    data QConfig = QConfig
    { hostname :: ByteString
    , port :: Int
    , authSiteKey :: System.IO.FilePath
    , cookieTimeout :: Maybe Int
    , pgsConfig :: PGSConfig
    -- , sslCert :: FilePath
    -- , sslKey :: FilePath
    -- , dbName :: String
    }
    loadQConfig :: System.IO.FilePath -> IO QConfig
    loadQConfig cfgFile = do
    env <- getEnvironment
    cfg <- C.load [C.Required cfgFile]
    let dbEnvCfg = pgsDefaultConfig . C.pack <$> lookup "DATABASE_URL" env
    readQConfig cfg dbEnvCfg
    readQConfig :: CT.Config -> Maybe PGSConfig -> IO QConfig
    readQConfig cfg pc =
    QConfig <$> C.lookupDefault "localhost" cfg "hostname"
    <*> C.lookupDefault 8000 cfg "port"
    <*> C.require cfg "siteKey"
    <*> C.lookup cfg "cookieTimeout"
    <*> maybe (mkPGSConfig $ C.subconfig "db" cfg) pure pc
    -- <*> (fmap fpFromText $ C.require cfg "sslCert")
    -- <*> (fmap fpFromText $ C.require cfg "sslKey")
    -- <*> C.require cfg "db"
    baseSnapConfig :: MonadSnap m => QConfig -> SC.Config m a -> SC.Config m a
    baseSnapConfig qc =
    SC.setHostname (hostname qc) .
    SC.setPort (port qc)
    --SC.setSSLPort (port qc) .
    --SC.setSSLCert (fpToString $ sslCert qc) .
    --SC.setSSLKey (fpToString $ sslKey cfg)
    -- configuration specific to Snap, commandLineConfig arguments override
    -- config file.
    snapConfig :: QConfig -> IO (SC.Config Snap a)
    snapConfig qc = SC.commandLineConfig $ baseSnapConfig qc SC.emptyConfig
  • edit in server/Main.hs at line 1
    [4.1040][4.1040:1109](),[4.89][4.5255:5256](),[4.128][4.5255:5256](),[4.1109][4.5255:5256](),[4.1314][4.5255:5256](),[4.1459][4.5255:5256](),[4.7548][4.5255:5256](),[4.5255][4.5255:5256]()
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE TemplateHaskell #-}
  • edit in server/Main.hs at line 5
    [4.35][4.112:152](),[4.1165][4.112:152](),[4.2439][4.112:152](),[4.152][4.123:170]()
    import qualified Data.Configurator as C
    import qualified Data.Configurator.Types as CT
  • edit in server/Main.hs at line 6
    [4.626][4.7720:7747]()
    import System.IO(FilePath)
  • edit in server/Main.hs at line 10
    [4.943]
    [2.3135]
    import Aftok.QConfig
  • edit in server/Main.hs at line 18
    [4.1609][4.1609:1633](),[4.1633][4.171:218]()
    import Snap.Http.Server
    import qualified Snap.Http.Server.Config as SC
  • edit in server/Main.hs at line 22
    [4.218][4.5492:5493](),[4.1477][4.5492:5493](),[4.1633][4.5492:5493](),[4.2593][4.5492:5493](),[4.7874][4.5492:5493](),[4.5492][4.5492:5493](),[4.1932][4.1932:1955](),[4.1955][4.340:383](),[4.383][4.7748:7786](),[4.7786][4.7903:7934](),[4.7903][4.7903:7934](),[4.7934][4.42:95](),[4.383][4.42:95](),[4.95][4.7935:7959](),[4.7959][4.309:314](),[4.309][4.309:314]()
    data QConfig = QConfig
    { hostname :: ByteString
    , port :: Int
    , authSiteKey :: System.IO.FilePath
    , cookieTimeout :: Maybe Int
    -- , sslCert :: FilePath
    -- , sslKey :: FilePath
    -- , dbName :: String
    }
  • edit in server/Main.hs at line 27
    [4.8257][4.8257:8316]()
    --simpleHttpServe sconf $ runReaderT (site sqliteQDB) db
  • replacement in server/Main.hs at line 34
    [4.3244][4.8653:8692](),[4.8653][4.8653:8692]()
    pgs <- nestSnaplet "db" db pgsInit
    [4.3244]
    [4.8692]
    pgs <- nestSnaplet "db" db $ pgsInit' pgsConfig
  • edit in server/Main.hs at line 64
    [4.252][4.9050:9051](),[4.9050][4.9050:9051](),[4.9051][4.8019:8067](),[4.8067][4.469:495](),[4.469][4.469:495](),[4.495][4.3939:3976](),[4.3976][4.496:633](),[4.555][4.496:633](),[4.633][4.96:143](),[4.143][4.3977:4016](),[4.4016][4.9110:9154](),[4.9110][4.9110:9154](),[4.9154][4.143:264](),[4.143][4.143:264](),[4.264][4.9155:9192](),[4.9192][4.829:962](),[4.829][4.829:962](),[4.962][4.265:411]()
    loadQConfig :: System.IO.FilePath -> IO QConfig
    loadQConfig cfgFile = do
    cfg <- C.load [C.Required cfgFile]
    parseQConfig cfg
    parseQConfig :: CT.Config -> IO QConfig
    parseQConfig cfg =
    QConfig <$> C.lookupDefault "localhost" cfg "hostname"
    <*> C.lookupDefault 8000 cfg "port"
    <*> C.require cfg "siteKey"
    <*> C.lookup cfg "cookieTimeout"
    -- <*> (fmap fpFromText $ C.require cfg "sslCert")
    -- <*> (fmap fpFromText $ C.require cfg "sslKey")
    -- <*> C.require cfg "db"
    baseSnapConfig :: MonadSnap m => QConfig -> SC.Config m a -> SC.Config m a
    baseSnapConfig cfg =
    SC.setHostname (hostname cfg) .
    SC.setPort (port cfg)
    --SC.setSSLPort (port cfg) .
    --SC.setSSLCert (fpToString $ sslCert cfg) .
    --SC.setSSLKey (fpToString $ sslKey cfg)
  • edit in server/Main.hs at line 65
    [4.1078][4.9193:9240](),[4.9240][4.1126:1197](),[4.1126][4.1126:1197](),[4.1197][4.1121:1122]()
    snapConfig :: QConfig -> IO (SC.Config Snap a)
    snapConfig cfg = SC.commandLineConfig $ baseSnapConfig cfg emptyConfig