about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.hs30
-rw-r--r--src/Auth.hs24
2 files changed, 36 insertions, 18 deletions
diff --git a/src/App.hs b/src/App.hs
index 4d9bf22db879..708dd896fab2 100644
--- a/src/App.hs
+++ b/src/App.hs
@@ -47,6 +47,11 @@ server dbFile = createAccount
            :<|> login
            :<|> logout
   where
+    -- Admit Admins + whatever the predicate `p` passes.
+    adminsAnd cookie p = Auth.assert dbFile cookie (\acct@T.Account{..} -> accountRole == T.Admin || p acct)
+    -- Admit Admins only.
+    adminsOnly cookie = adminsAnd cookie (const True)
+
     -- TODO(wpcarro): Handle failed CONSTRAINTs instead of sending 500s
     createAccount :: T.CreateAccountRequest -> Handler NoContent
     createAccount request = do
@@ -58,26 +63,23 @@ server dbFile = createAccount
       pure NoContent
 
     deleteAccount :: T.SessionCookie -> Text -> Handler NoContent
-    deleteAccount cookie username = do
-      mRole <- liftIO $ Auth.roleFromCookie dbFile cookie
-      case mRole of
-        Just T.Admin -> do
-          liftIO $ Accounts.delete dbFile (T.Username username)
-          pure NoContent
-        -- cannot delete an account if you're not an Admin
-        _ -> throwError err401 { errBody = "Only admins can delete accounts." }
+    deleteAccount cookie username = adminsOnly cookie $ do
+      liftIO $ Accounts.delete dbFile (T.Username username)
+      pure NoContent
 
     listAccounts :: T.SessionCookie -> Handler [T.User]
-    listAccounts (T.SessionCookie cookie) = liftIO $ Accounts.list dbFile
+    listAccounts cookie = adminsOnly cookie $ do
+      liftIO $ Accounts.list dbFile
 
     createTrip :: T.SessionCookie -> T.Trip -> Handler NoContent
-    createTrip cookie trip = do
-      liftIO $ Trips.create dbFile trip
-      pure NoContent
+    createTrip cookie trip@T.Trip{..} =
+      adminsAnd cookie (\T.Account{..} -> accountUsername == tripUsername) $ do
+        liftIO $ Trips.create dbFile trip
+        pure NoContent
 
-    -- TODO(wpcarro): Validate incoming data like startDate.
     deleteTrip :: T.SessionCookie -> T.TripPK -> Handler NoContent
-    deleteTrip cookie tripPK = do
+    deleteTrip cookie tripPK@T.TripPK{..} =
+      adminsAnd cookie (\T.Account{..} -> accountUsername == tripPKUsername) $ do
       liftIO $ Trips.delete dbFile tripPK
       pure NoContent
 
diff --git a/src/Auth.hs b/src/Auth.hs
index 6a24360584a2..4962ce50abef 100644
--- a/src/Auth.hs
+++ b/src/Auth.hs
@@ -3,9 +3,13 @@
 --------------------------------------------------------------------------------
 module Auth where
 --------------------------------------------------------------------------------
+import Control.Monad.IO.Class (liftIO)
+import Data.String.Conversions (cs)
 import Database.SQLite.Simple
 import Utils
 import Web.Cookie
+import Servant
+import Servant.Server.Internal.ServerError
 
 import qualified Data.UUID as UUID
 import qualified Web.Cookie as WC
@@ -22,9 +26,9 @@ uuidFromCookie (T.SessionCookie cookies) = do
   uuid <- UUID.fromASCIIBytes auth
   pure $ T.SessionUUID uuid
 
--- | Attempt to return the user role associated with the `cookie`.
-roleFromCookie :: FilePath -> T.SessionCookie -> IO (Maybe T.Role)
-roleFromCookie dbFile cookie = withConnection dbFile $ \conn -> do
+-- | Attempt to return the account associated with `cookie`.
+accountFromCookie :: FilePath -> T.SessionCookie -> IO (Maybe T.Account)
+accountFromCookie dbFile cookie = withConnection dbFile $ \conn -> do
   case uuidFromCookie cookie of
     Nothing -> pure Nothing
     Just uuid -> do
@@ -35,7 +39,7 @@ roleFromCookie dbFile cookie = withConnection dbFile $ \conn -> do
           mAccount <- Accounts.lookup dbFile storedSessionUsername
           case mAccount of
             Nothing -> pure Nothing
-            Just T.Account{..} -> pure (Just accountRole)
+            Just x -> pure (Just x)
 
 -- | Create a new session cookie.
 mkCookie :: T.SessionUUID -> SetCookie
@@ -52,3 +56,15 @@ emptyCookie =
     { setCookieName = "auth"
     , setCookieValue = ""
     }
+
+-- | Throw a 401 error if the `predicate` fails.
+assert :: FilePath -> T.SessionCookie -> (T.Account -> Bool) -> Handler a -> Handler a
+assert dbFile cookie predicate handler = do
+  mRole <- liftIO $ accountFromCookie dbFile cookie
+  case mRole of
+    Nothing -> throwError err401 { errBody = "Missing valid session cookie" }
+    Just account ->
+      if predicate account then
+        handler
+      else
+        throwError err401 { errBody = "You are not authorized to access this resource" }