about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/API.hs13
-rw-r--r--src/App.hs62
-rw-r--r--src/Types.hs10
3 files changed, 58 insertions, 27 deletions
diff --git a/src/API.hs b/src/API.hs
index c84da5aef917..50263bb3e69c 100644
--- a/src/API.hs
+++ b/src/API.hs
@@ -5,10 +5,14 @@ module API where
 --------------------------------------------------------------------------------
 import Data.Text
 import Servant.API
+import Web.Cookie
 
 import qualified Types as T
 --------------------------------------------------------------------------------
 
+-- | Once authenticated, users receive a SessionCookie.
+type SessionCookie = Header' '[Required] "Set-Cookie" T.SessionCookie
+
 type API =
       -- accounts: Create
            "accounts"
@@ -18,20 +22,24 @@ type API =
       -- accounts: Update
       -- accounts: Delete
       :<|> "accounts"
+           :> SessionCookie
            :> QueryParam' '[Required] "username" Text
            :> Delete '[JSON] NoContent
       -- accounts: List
       :<|> "accounts"
+           :> SessionCookie
            :> Get '[JSON] [T.User]
 
       -- trips: Create
       :<|> "trips"
+           :> SessionCookie
            :> ReqBody '[JSON] T.Trip
            :> Post '[JSON] NoContent
       -- trips: Read
       -- trips: Update
       -- trips: Delete
       :<|> "trips"
+           :> SessionCookie
            :> ReqBody '[JSON] T.TripPK
            :> Delete '[JSON] NoContent
       -- trips: List
@@ -41,4 +49,7 @@ type API =
       -- Miscellaneous
       :<|> "login"
            :> ReqBody '[JSON] T.AccountCredentials
-           :> Post '[JSON] NoContent
+           :> Post '[JSON] (Headers '[Header "Set-Cookie" SetCookie] NoContent)
+      :<|> "logout"
+           :> SessionCookie
+           :> Get '[JSON] (Headers '[Header "Set-Cookie" SetCookie] NoContent)
diff --git a/src/App.hs b/src/App.hs
index 209e2f209231..783b4402f078 100644
--- a/src/App.hs
+++ b/src/App.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NamedFieldPuns #-}
@@ -14,6 +15,7 @@ import Network.Wai.Handler.Warp as Warp
 import Servant
 import API
 import Utils
+import Web.Cookie
 
 import qualified Crypto.KDF.BCrypt as BC
 import qualified Data.Text.Encoding as TE
@@ -32,14 +34,16 @@ server dbFile = createAccountH
            :<|> deleteTripH
            :<|> listTripsH
            :<|> loginH
+           :<|> logoutH
   where
-    createAccountH newUser  = liftIO $ createAccount newUser
-    deleteAccountH username = liftIO $ deleteAccount username
-    listAccountsH           = liftIO $ listAccounts
-    createTripH trip        = liftIO $ createTrip trip
-    deleteTripH tripPK      = liftIO $ deleteTrip tripPK
-    listTripsH              = liftIO $ listTrips
-    loginH creds            = liftIO $ login creds
+    createAccountH newUser         = liftIO $ createAccount newUser
+    deleteAccountH cookie username = liftIO $ deleteAccount cookie username
+    listAccountsH cookie           = liftIO $ listAccounts cookie
+    createTripH cookie trip        = liftIO $ createTrip cookie trip
+    deleteTripH cookie tripPK      = liftIO $ deleteTrip cookie tripPK
+    listTripsH                     = liftIO $ listTrips
+    loginH creds                   = liftIO $ login creds
+    logoutH cookie                 = liftIO $ logout cookie
 
     -- TODO(wpcarro): Handle failed CONSTRAINTs instead of sending 500s
     createAccount :: T.CreateAccountRequest -> IO NoContent
@@ -51,30 +55,30 @@ server dbFile = createAccountH
         (T.createAccountRequestRole request)
       pure NoContent
 
-    deleteAccount :: Text -> IO NoContent
-    deleteAccount username = do
+    deleteAccount :: T.SessionCookie -> Text -> IO NoContent
+    deleteAccount cookie username = do
       Accounts.delete dbFile (T.Username username)
       pure NoContent
 
-    listAccounts :: IO [T.User]
-    listAccounts = Accounts.list dbFile
+    listAccounts :: T.SessionCookie -> IO [T.User]
+    listAccounts cookie = Accounts.list dbFile
 
-    createTrip :: T.Trip -> IO NoContent
-    createTrip trip = do
+    createTrip :: T.SessionCookie -> T.Trip -> IO NoContent
+    createTrip cookie trip = do
       Trips.create dbFile trip
       pure NoContent
 
-    listTrips :: IO [T.Trip]
-    listTrips = Trips.list dbFile
-
     -- TODO(wpcarro): Validate incoming data like startDate.
-    deleteTrip :: T.TripPK -> IO NoContent
-    deleteTrip tripPK = do
+    deleteTrip :: T.SessionCookie -> T.TripPK -> IO NoContent
+    deleteTrip cookie tripPK = do
       Trips.delete dbFile tripPK
       pure NoContent
 
-    -- TODO(wpcarro): Create and store a session token
-    login :: T.AccountCredentials -> IO NoContent
+    listTrips :: IO [T.Trip]
+    listTrips = Trips.list dbFile
+
+    login :: T.AccountCredentials
+          -> IO (Headers '[Header "Set-Cookie" SetCookie] NoContent)
     login (T.AccountCredentials username password) = do
       mAccount <- Accounts.lookup dbFile username
       case mAccount of
@@ -85,25 +89,31 @@ server dbFile = createAccountH
               if T.passwordsMatch password accountPassword then do
                 session <- Sessions.findOrCreate dbFile account
                 -- set cookie
-                pure NoContent
+                undefined
               else do
                 LoginAttempts.increment dbFile username
-                throwIO $ err401 { errBody = "Your credentials are invalid" }
+                throwIO err401 { errBody = "Your credentials are invalid" }
             Just attempts ->
               if attempts > 3 then
                 -- TODO(wpcarro): Prefer 429 error code
-                throwIO $ err401 { errBody = "Too many failed login attempts" }
+                throwIO err401 { errBody = "Too many failed login attempts" }
               else if T.passwordsMatch password accountPassword then do
                 session <- Sessions.findOrCreate dbFile account
                 -- set cookie
-                pure NoContent
+                undefined
               else do
                 LoginAttempts.increment dbFile username
                 -- TODO(wpcarro): Catch and return errors over HTTP
-                throwIO $ err401 { errBody = "Your credentials are invalid" }
+                throwIO err401 { errBody = "Your credentials are invalid" }
 
         -- In this branch, the user didn't supply a known username.
-        Nothing -> throwIO $ err401 { errBody = "Your credentials are invalid" }
+        Nothing -> throwIO err401 { errBody = "Your credentials are invalid" }
+
+    logout :: T.SessionCookie
+           -> IO (Headers '[Header "Set-Cookie" SetCookie] NoContent)
+    logout cookie = undefined
+      -- pull off SessionUUID from the request headers
+      -- delete the SessionUUID from the Sessions table.
 
 mkApp :: FilePath -> IO Application
 mkApp dbFile = do
diff --git a/src/Types.hs b/src/Types.hs
index d33ea6870f13..eed9bf8c1696 100644
--- a/src/Types.hs
+++ b/src/Types.hs
@@ -14,6 +14,8 @@ import Database.SQLite.Simple.Ok
 import Database.SQLite.Simple.FromField
 import Database.SQLite.Simple.ToField
 import GHC.Generics
+import Web.Cookie
+import Servant.API
 import Crypto.Random.Types (MonadRandom)
 
 import qualified Crypto.KDF.BCrypt as BC
@@ -382,3 +384,11 @@ data LoginAttempt = LoginAttempt
 
 instance FromRow LoginAttempt where
   fromRow = LoginAttempt <$> field <*> field
+
+newtype SessionCookie = SessionCookie Cookies
+
+instance FromHttpApiData SessionCookie where
+  parseHeader x =
+    x |> parseCookies |> SessionCookie |> pure
+  parseQueryParam x =
+    x |> TE.encodeUtf8 |> parseCookies |> SessionCookie |> pure