Add logout functionality.

[?]
Aug 18, 2020, 6:05 PM
PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC

Dependencies

  • [2] WRPIYG3E Use project listing functionality to check for whether we have a cookie.
  • [3] O722AOKE Add route to allow crediting of events to users/projects.
  • [4] I2KHGVD4 Require project permissions for access to most data.
  • [5] ARX7SHY5 Begin work on login UI.
  • [6] IZEVQF62 Work in progress replacing sqlite with postgres.
  • [7] NEDDHXUK Reformat via stylish-haskell
  • [8] O5FVTOM6 Undo JSON silliness, enable a couple more routes.
  • [9] HO2PFRAB Client login now handles response correctly.
  • [10] GMYPBCWE Make docker-compose work.
  • [11] 2G3GNDDU Event logging is now functioning in postgres.
  • [12] TUA4HMUD Use real API capability for login.
  • [13] 2XQD6KKK Add invitation logic and clean up DBProg error handling.
  • [14] HMDM3B55 Implement core of payments/billing infrastructure.
  • [15] EA5BFM5G Split Login component into its own module.
  • [16] JXG3FCXY Upgrade ps + halogen versions.
  • [17] PBD7LZYQ Postgres & auth are beginning to function.
  • [18] TKGBRIQT Login component now raises LoginComplete message.
  • [19] BFZN4SUA Make timeline component work.
  • [20] 3LMXT7Z6 preventDefault on login form submission.
  • [21] IPG33FAW Add billing daemon
  • [22] NJNMO72S Add zcash.com submodule and update client to modern halogen.
  • [23] B6HWAPDP Modularize & update to recent haskoin.
  • [24] EFSXYZPO Autoformat everything with brittany.
  • [*] RB2ETNIF Add skeletal PureScript client project.
  • [*] 4U7F3CPI THE GREAT RENAMING OF THINGS!
  • [*] ADMKQQGC Initial empty Snap project.

Change contents

  • edit in client/dev/index.html at line 7
    [3.2649]
    [3.2649]
    <link rel="stylesheet" type="text/css" href="./assets/css/spinner.css" />
  • edit in client/src/Aftok/Login.purs at line 9
    [3.618][3.295343:295382]()
    import Data.HTTP.Method (Method(POST))
  • replacement in client/src/Aftok/Login.purs at line 13
    [3.30][3.295408:295460](),[3.295408][3.295408:295460]()
    import Affjax (request, defaultRequest, printError)
    [3.30]
    [3.295460]
    import Affjax (post, get, printError)
  • edit in client/src/Aftok/Login.purs at line 16
    [3.295534]
    [3.154]
    import Affjax.ResponseFormat as RF
  • edit in client/src/Aftok/Login.purs at line 56
    [3.296080]
    [3.296080]
    , checkLogin :: m LoginResponse
    , logout :: m Unit
  • replacement in client/src/Aftok/Login.purs at line 178
    [3.320][3.300621:300846](),[3.2736][3.300621:300846]()
    result <- request $
    defaultRequest { method = Left POST
    , url = "/api/login"
    , content = Just <<< RB.Json <<< encodeJson $ { username: user, password : pass }
    }
    [3.320]
    [3.321]
    result <- post RF.ignore "/api/login" (Just <<< RB.Json <<< encodeJson $ { username: user, password : pass })
  • replacement in client/src/Aftok/Login.purs at line 180
    [3.338][3.338:434]()
    Left err -> log (printError err)
    Right r -> log ("Got status: " <> show r.status)
    [3.338]
    [3.300846]
    Left err -> log ("Login failed: " <> printError err)
    Right r -> log ("Login status: " <> show r.status)
  • edit in client/src/Aftok/Login.purs at line 188
    [3.301097]
    [3.3162]
    checkLogin :: Aff LoginResponse
    checkLogin = do
    log "Sending login check to /api/login/check ..."
    result <- get RF.ignore "/api/login/check"
    case result of
    Left err -> do
    log ("Login failed: " <> printError err)
    pure $ Error { status: Nothing, message: printError err }
    Right r -> do
    log ("Login status: " <> show r.status)
    pure $ case r.status of
    StatusCode 200 -> OK
    StatusCode _ -> Forbidden
  • edit in client/src/Aftok/Login.purs at line 203
    [3.3163]
    [3.301098]
    logout :: Aff Unit
    logout = do
    log "Logging out on server with /api/logout ..."
    result <- get RF.ignore "/api/logout"
    case result of
    Left err -> log ("Logout failed: " <> printError err)
    Right r -> log ("Logout status: " <> show r.status)
  • replacement in client/src/Aftok/Login.purs at line 212
    [3.301130][3.301130:301156]()
    apiCapability = { login }
    [3.301130]
    [3.3163]
    apiCapability = { login, checkLogin, logout }
  • replacement in client/src/Aftok/Login.purs at line 215
    [3.493][3.301190:301234](),[3.301190][3.301190:301234]()
    mockCapability = { login: \_ _ -> pure OK }
    [3.493]
    mockCapability =
    { login: \_ _ -> pure OK
    , checkLogin: pure OK
    , logout: pure unit
    }
  • edit in client/src/Main.purs at line 7
    [3.493][2.3722:3754]()
    import Data.Either (Either(..))
  • edit in client/src/Main.purs at line 15
    [3.307659]
    [3.307659]
    import Halogen.VDom.Driver (runUI)
    import Halogen.HTML.Core (ClassName(..))
  • replacement in client/src/Main.purs at line 19
    [3.307685][3.7000:7035](),[3.7000][3.7000:7035]()
    import Halogen.VDom.Driver (runUI)
    [3.307685]
    [3.1094]
    -- import Halogen.HTML.CSS as CSS
    import Halogen.HTML.Events as E
    import Halogen.HTML.Properties as P
  • edit in client/src/Main.purs at line 26
    [2.3787][3.307786:307824](),[3.307786][3.307786:307824]()
    -- import Effect.Class.Console (info)
  • replacement in client/src/Main.purs at line 34
    [2.3826][2.3826:3897]()
    mainc = component login timeline project
    runUI mainc unit body
    [2.3826]
    [3.1116]
    mainComponent = component login timeline project
    runUI mainComponent unit body
  • replacement in client/src/Main.purs at line 38
    [3.308035][3.308035:308049]()
    = LoggedIn
    [3.308035]
    [3.308049]
    = Loading
  • edit in client/src/Main.purs at line 40
    [3.308063]
    [3.308063]
    | LoggedIn
  • edit in client/src/Main.purs at line 45
    [2.3951]
    [3.1118]
    | Logout
  • edit in client/src/Main.purs at line 74
    [3.308688]
    [3.308688]
    Loading ->
    HH.div [P.classes [ClassName "loader"]] [HH.text "Loading..."]
  • replacement in client/src/Main.purs at line 78
    [3.308708][3.1832:1929]()
    HH.div_ [ HH.slot _login unit (Login.component loginCap) unit (Just <<< LoginComplete) ]
    [3.308708]
    [3.308805]
    HH.div_
    [ HH.slot _login unit (Login.component loginCap) unit (Just <<< LoginComplete) ]
  • replacement in client/src/Main.purs at line 82
    [3.308824][2.4141:4240]()
    HH.div_ [ HH.slot _timeline unit (Timeline.component tlCap { width: 600.0 }) unit absurd ]
    [3.308824]
    [3.308924]
    withNavBar $ HH.div_
    [ HH.slot _timeline unit (Timeline.component tlCap { width: 600.0 }) unit absurd ]
  • replacement in client/src/Main.purs at line 88
    [2.4264][2.4264:4407]()
    projects <- lift pCap.listProjects
    case projects of
    Left err -> H.put LoggedOut
    Right _ -> H.put LoggedIn
    [2.4264]
    [2.4407]
    result <- lift loginCap.checkLogin
    case result of
    Login.Forbidden -> H.put LoggedOut
    _ -> H.put LoggedIn
  • replacement in client/src/Main.purs at line 95
    [2.4480][2.4480:4489]()
    [2.4480]
    Logout -> do
    lift loginCap.logout
    H.put LoggedOut
    withNavBar :: forall s m. H.ComponentHTML MainAction s m -> H.ComponentHTML MainAction s m
    withNavBar body =
    HH.div_
    [HH.nav
    [P.classes (ClassName <$> ["navbar", "navbar-expand-lg", "navbar-light", "bg-white"])]
    [HH.div
    [P.classes (ClassName <$> ["container-fluid"])]
    [brand, logout]
    ]
    ,body
    ]
    brand :: forall a s m. H.ComponentHTML a s m
    brand = HH.div
    [P.classes (ClassName <$> ["navbar-brand"])]
    [HH.h4
    [P.classes (ClassName <$> ["font-weight-bold"])]
    [HH.text "Aftok"]
    ]
    logout :: forall s m. H.ComponentHTML MainAction s m
    logout = HH.button
    [P.classes (ClassName <$> ["btn", "navbar-btn", "btn-sm", "btn-primary", "lift", "ml-auto"])
    ,E.onClick \_ -> Just Logout
    ]
    [HH.text "Logout"]
    -- <!-- Navigation -->
    -- <ul class="navbar-nav ml-auto">
    -- <li class="nav-item dropdown">
    -- <a class="nav-link dropdown-toggle" id="navbarAccount" data-toggle="dropdown" href="#" aria-haspopup="true" aria-expanded="false">
    -- Guidebook
    -- </a>
    -- <ul class="dropdown-menu" aria-labelledby="navbarAccount">
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-link dropdown-toggle" data-toggle="dropdown" href="#">
    -- Getting Started
    -- </a>
    -- <div class="dropdown-menu">
    -- <a class="dropdown-item" href="@@webRoot/guide-foundation.html">
    -- Foundational Principles
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-time.html">
    -- Measuring Contributions
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-revenue.html">
    -- Revenue Sharing
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-tithing.html">
    -- Varying Compensation
    -- </a>
    -- </div>
    -- </li>
    -- <!--
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-link dropdown-toggle" data-toggle="dropdown" href="#">
    -- Up And Running
    -- </a>
    -- <div class="dropdown-menu">
    -- <a class="dropdown-item" href="@@webRoot/guide-voting.html">
    -- Share-Weighted Voting
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-auctions.html">
    -- Expense Auctions
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-forks.html">
    -- Project Forks & Merges
    -- </a>
    -- </div>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-link dropdown-toggle" data-toggle="dropdown" href="#">
    -- Coming Soon!
    -- </a>
    -- <div class="dropdown-menu">
    -- <a class="dropdown-item" href="@@webRoot/guide-debt-contracts.html">
    -- 3rd-party Contracts
    -- </a>
    -- <a class="dropdown-item" href="@@webRoot/guide-delegative-voting.html">
    -- Delegative Voting
    -- </a>
    -- </div>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/blog.html">
    -- Blog
    -- </a>
    -- </li>
    -- -->
    -- </ul>
    -- </li>
    -- <li class="nav-item dropdown">
    -- <a class="nav-link dropdown-toggle" id="navbarAccount" data-toggle="dropdown" href="#" aria-haspopup="true" aria-expanded="false">
    -- My Account
    -- </a>
    -- <ul class="dropdown-menu" aria-labelledby="navbarAccount">
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" data-toggle="modal" href="#modalSigninHorizontal">
    -- Sign In
    -- </a>
    -- </li>
    -- <!--
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-revenue.html">
    -- Revenue Dashboard
    -- </a>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-auctions.html">
    -- Active Expense Auctions
    -- </a>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-general.html">
    -- Project List
    -- </a>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-tithes.html">
    -- My Tithes
    -- </a>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-keys.html">
    -- Manage Payment Keys
    -- </a>
    -- </li>
    -- <li class="dropdown-item dropright">
    -- <a class="dropdown-item" href="@@webRoot/account-billing.html">
    -- Billing Center
    -- </a>
    -- </li>
    -- -->
    -- </ul>
    -- </li>
    -- <li class="nav-item dropdown">
    -- <a class="nav-link dropdown-toggle" id="navbarDocumentation" data-toggle="dropdown" href="#" aria-haspopup="true" aria-expanded="false">
    -- Documentation
    -- </a>
    -- <div class="dropdown-menu dropdown-menu-md" aria-labelledby="navbarDocumentation">
    -- <div class="list-group list-group-flush">
    -- <a class="list-group-item" href="@@webRoot/docs/index.html">
    --
    -- <!-- Icon -->
    -- <div class="icon icon-sm text-primary">
    -- @@include("../assets/img/icons/duotone-icons/General/Clipboard.svg")
    -- </div>
    --
    -- <!-- Content -->
    -- <div class="ml-4">
    --
    -- <!-- Heading -->
    -- <h6 class="font-weight-bold text-uppercase text-primary mb-0">
    -- Documentation
    -- </h6>
    --
    -- <!-- Text -->
    -- <p class="font-size-sm text-gray-700 mb-0">
    -- CLI & API user guide
    -- </p>
    --
    -- </div>
    --
    -- </a>
    -- <a class="list-group-item" href="@@webRoot/faq.html">
    --
    -- <!-- Icon -->
    -- <div class="icon icon-sm text-primary">
    -- @@include("../assets/img/icons/duotone-icons/Code/Question-circle.svg")
    -- </div>
    --
    -- <!-- Content -->
    -- <div class="ml-4">
    --
    -- <!-- Heading -->
    -- <h6 class="font-weight-bold text-uppercase text-primary mb-0">
    -- FAQ
    -- </h6>
    --
    -- <!-- Text -->
    -- <p class="font-size-sm text-gray-700 mb-0">
    -- Common problems & solutions
    -- </p>
    --
    -- </div>
    --
    -- </a>
    -- <a class="list-group-item" href="https://discord.gg/wbhCGjw" target="_blank">
    --
    -- <!-- Icon -->
    -- <div class="icon icon-sm text-primary">
    -- @@include("../assets/img/icons/social/discord.svg")
    -- </div>
    --
    -- <!-- Content -->
    -- <div class="ml-4">
    --
    -- <!-- Heading -->
    -- <h6 class="font-weight-bold text-uppercase text-primary mb-0">
    -- Community
    -- </h6>
    --
    -- <!-- Text -->
    -- <p class="font-size-sm text-gray-700 mb-0">
    -- Join our Discord chat
    -- </p>
    --
    -- </div>
    --
    -- </a>
    -- <!--
    -- <a class="list-group-item" href="@@webRoot/docs/changelog.html">
    --
    -- <div class="icon icon-sm text-primary">
    -- @@include("../assets/img/icons/duotone-icons/Files/File.svg")
    -- </div>
    --
    -- <div class="ml-4">
    --
    -- <h6 class="font-weight-bold text-uppercase text-primary mb-0">
    -- Changelog
    -- </h6>
    --
    -- <p class="font-size-sm text-gray-700 mb-0">
    -- Project history
    -- </p>
    -- </div>
    -- </a>
    -- -->
    -- </div>
    -- </div>
    -- </li>
    -- </ul>
  • edit in server/Aftok/Snaplet/Auth.hs at line 1
    [3.203]
    [27.2136]
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE TypeApplications #-}
  • edit in server/Aftok/Snaplet/Auth.hs at line 13
    [3.47590]
    [3.423]
    import Data.Text ( Text )
  • edit in server/Aftok/Snaplet/Auth.hs at line 35
    [3.1227]
    [3.47767]
    requireLoginWith (const throwChallenge)
    requireLoginWith :: (forall a. () -> S.Handler App App a) -> S.Handler App App AU.AuthUser
    requireLoginWith throwMissingAuth = do
  • replacement in server/Aftok/Snaplet/Auth.hs at line 40
    [3.47796][3.1247:1323](),[3.1247][3.1247:1323]()
    rawHeader <- maybe throwChallenge pure $ getHeader "Authorization" req
    [3.47796]
    [3.47797]
    rawHeader <- maybe (throwMissingAuth ()) pure $ getHeader "Authorization" req
  • replacement in server/Aftok/Snaplet/Auth.hs at line 60
    [3.1644][3.1644:1682]()
    maybe requireLogin pure currentUser
    [3.1644]
    [3.1682]
    maybe (requireLoginWith $ const (throwDenied $ AU.AuthError "Not Authenticated")) pure currentUser
  • replacement in server/Aftok/Snaplet/Auth.hs at line 66
    [3.33577][3.33577:33653]()
    (snapError 403 "Unable to retrieve user record for authenticated user")
    [3.33577]
    [3.33653]
    (snapError 500 "Unable to retrieve user record for authenticated user")
  • replacement in server/Aftok/Snaplet/Auth.hs at line 80
    [3.48184][3.48184:48232]()
    writeText $ "Access Denied: " <> show failure
    [3.48184]
    [3.48232]
    logError (encodeUtf8 $ "Access Denied: " <> show @Text failure)
  • edit in server/Main.hs at line 34
    [3.11663]
    [3.11724]
    import qualified Snap.Snaplet.Auth as AU
  • replacement in server/Main.hs at line 49
    [3.12436][3.62674:62847]()
    sesss <- nestSnaplet "sessions" sess $ initCookieSessionManager
    (cfg ^. authSiteKey . to encodeString)
    "quookie"
    (Just "aftok.com")
    (cfg ^. cookieTimeout)
    [3.12436]
    [3.26670]
    let cookieKey = cfg ^. authSiteKey . to encodeString
    sesss <- nestSnaplet "sessions" sess $ initCookieSessionManager cookieKey "quookie" Nothing (cfg ^. cookieTimeout)
  • replacement in server/Main.hs at line 57
    [3.38518][3.62916:62984]()
    loginRoute = method GET requireLogin >> redirect "/home"
    [3.38518]
    [3.311217]
    loginRoute = method GET requireLogin >> redirect "/app"
  • edit in server/Main.hs at line 59
    [3.311276]
    [3.63040]
    checkLoginRoute = void $ method GET requireUser
    logoutRoute = method GET (with auth AU.logout)
  • edit in server/Main.hs at line 110
    [3.65091]
    [3.65091]
    , ("logout" , logoutRoute)
    , ("login/check" , checkLoginRoute)