assume dashboard is public when there are no users in connected datastore. #117

This commit is contained in:
Danny van Kooten 2018-09-12 09:36:59 +02:00
parent 291d53fcaf
commit 221e6394de
5 changed files with 75 additions and 24 deletions

View File

@ -16,6 +16,10 @@ class LogoutButton extends Component {
} }
render() { render() {
if(document.cookie.indexOf('auth') < 0) {
return ''
}
return ( return (
<a href="#" onClick={this.handleSubmit}>Sign out</a> <a href="#" onClick={this.handleSubmit}>Sign out</a>
) )

View File

@ -17,6 +17,7 @@ class Dashboard extends Component {
period: (window.location.hash.substring(2) || 'last-7-days'), period: (window.location.hash.substring(2) || 'last-7-days'),
before: 0, before: 0,
after: 0, after: 0,
isPublic: document.cookie.indexOf('auth') < 0,
} }
} }
@ -27,6 +28,11 @@ class Dashboard extends Component {
} }
render(props, state) { render(props, state) {
// only show logout link if this dashboard is not public
let logoutMenuItem = state.isPublic ? '' : (
<li class="signout"><span class="spacer">&middot;</span><LogoutButton onSuccess={props.onLogout} /></li>
);
return ( return (
<div class="app-page wrapper"> <div class="app-page wrapper">
@ -34,9 +40,8 @@ class Dashboard extends Component {
<nav class="main-nav animated fadeInDown"> <nav class="main-nav animated fadeInDown">
<ul> <ul>
<li class="logo"><a href="/">Fathom</a></li> <li class="logo"><a href="/">Fathom</a></li>
<li class="visitors"><Realtime onError={props.onLogout} /></li> <li class="visitors"><Realtime /></li>
<li class="spacer">&middot;</li> {logoutMenuItem}
<li class="signout"><LogoutButton onSuccess={props.onLogout} /></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@ -4,6 +4,7 @@ import { h, render, Component } from 'preact'
import Login from './pages/login.js' import Login from './pages/login.js'
import Dashboard from './pages/dashboard.js' import Dashboard from './pages/dashboard.js'
import { bind } from 'decko'; import { bind } from 'decko';
import Client from './lib/client.js';
class App extends Component { class App extends Component {
constructor(props) { constructor(props) {
@ -12,6 +13,16 @@ class App extends Component {
this.state = { this.state = {
authenticated: document.cookie.indexOf('auth') > -1 authenticated: document.cookie.indexOf('auth') > -1
} }
this.fetchAuthStatus()
}
@bind
fetchAuthStatus() {
Client.request(`session`)
.then((d) => {
this.setState({ authenticated: d })
})
} }
@bind @bind

View File

@ -1,7 +1,6 @@
package api package api
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"strings" "strings"
@ -25,8 +24,30 @@ func (l *login) Sanitize() {
l.Email = strings.ToLower(strings.TrimSpace(l.Email)) l.Email = strings.ToLower(strings.TrimSpace(l.Email))
} }
// GET /api/session
func (api *API) GetSession(w http.ResponseWriter, r *http.Request) error {
userCount, err := api.database.CountUsers()
if err != nil {
return err
}
// if 0 users in database, dashboard is public
if userCount == 0 {
return respond(w, envelope{Data: true})
}
// if existing session, assume logged-in
session, _ := api.sessions.Get(r, "auth")
if !session.IsNew {
respond(w, envelope{Data: true})
}
// otherwise: not logged-in yet
return respond(w, envelope{Data: false})
}
// URL: POST /api/session // URL: POST /api/session
func (api *API) LoginHandler(w http.ResponseWriter, r *http.Request) error { func (api *API) CreateSession(w http.ResponseWriter, r *http.Request) error {
// check login creds // check login creds
var l login var l login
err := json.NewDecoder(r.Body).Decode(&l) err := json.NewDecoder(r.Body).Decode(&l)
@ -59,7 +80,7 @@ func (api *API) LoginHandler(w http.ResponseWriter, r *http.Request) error {
} }
// URL: DELETE /api/session // URL: DELETE /api/session
func (api *API) LogoutHandler(w http.ResponseWriter, r *http.Request) error { func (api *API) DeleteSession(w http.ResponseWriter, r *http.Request) error {
session, _ := api.sessions.Get(r, "auth") session, _ := api.sessions.Get(r, "auth")
if !session.IsNew { if !session.IsNew {
session.Options.MaxAge = -1 session.Options.MaxAge = -1
@ -79,27 +100,35 @@ func (api *API) Authorize(next http.Handler) http.Handler {
// see http://www.gorillatoolkit.org/pkg/sessions#overview // see http://www.gorillatoolkit.org/pkg/sessions#overview
defer gcontext.Clear(r) defer gcontext.Clear(r)
session, err := api.sessions.Get(r, "auth") // first count users in datastore
// an err is returned if cookie has been tampered with, so check that // if 0, assume dashboard is public
userCount, err := api.database.CountUsers()
if err != nil { if err != nil {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusInternalServerError)
return return
} }
userID, ok := session.Values["user_id"] if userCount > 0 {
if session.IsNew || !ok { session, err := api.sessions.Get(r, "auth")
w.WriteHeader(http.StatusUnauthorized) // an err is returned if cookie has been tampered with, so check that
return if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
userID, ok := session.Values["user_id"]
if session.IsNew || !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
// validate user ID in session
if _, err := api.database.GetUser(userID.(int64)); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
} }
// find user next.ServeHTTP(w, r)
u, err := api.database.GetUser(userID.(int64))
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userKey, u)
next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }

View File

@ -11,8 +11,10 @@ func (api *API) Routes() *mux.Router {
// register routes // register routes
r := mux.NewRouter() r := mux.NewRouter()
r.Handle("/collect", NewCollector(api.database)).Methods(http.MethodGet) r.Handle("/collect", NewCollector(api.database)).Methods(http.MethodGet)
r.Handle("/api/session", HandlerFunc(api.LoginHandler)).Methods(http.MethodPost)
r.Handle("/api/session", HandlerFunc(api.LogoutHandler)).Methods(http.MethodDelete) r.Handle("/api/session", HandlerFunc(api.GetSession)).Methods(http.MethodGet)
r.Handle("/api/session", HandlerFunc(api.CreateSession)).Methods(http.MethodPost)
r.Handle("/api/session", HandlerFunc(api.DeleteSession)).Methods(http.MethodDelete)
r.Handle("/api/stats/site", api.Authorize(HandlerFunc(api.GetSiteStatsHandler))).Methods(http.MethodGet) r.Handle("/api/stats/site", api.Authorize(HandlerFunc(api.GetSiteStatsHandler))).Methods(http.MethodGet)
r.Handle("/api/stats/site/groupby/day", api.Authorize(HandlerFunc(api.GetSiteStatsPerDayHandler))).Methods(http.MethodGet) r.Handle("/api/stats/site/groupby/day", api.Authorize(HandlerFunc(api.GetSiteStatsPerDayHandler))).Methods(http.MethodGet)