Check admin code for admin pages
This commit is contained in:
parent
3ad9b2955f
commit
4eca005046
@ -3,6 +3,7 @@ package back
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@ -13,6 +14,15 @@ import (
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type UnauthorizedError struct {
|
||||
EventID string
|
||||
AdminCode string
|
||||
}
|
||||
|
||||
func (u UnauthorizedError) Error() string {
|
||||
return fmt.Sprintf("unauthorized: EventID = %s, AdminCode = %s", u.EventID, u.AdminCode)
|
||||
}
|
||||
|
||||
type GenString func(length int) (string, error)
|
||||
|
||||
type Store struct {
|
||||
@ -164,6 +174,37 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
|
||||
return
|
||||
}
|
||||
|
||||
type CheckEventAdminCodeQuery struct {
|
||||
EventID string
|
||||
AdminCode string
|
||||
}
|
||||
|
||||
func (s *Store) AuthorizeEventAdmin(ctx context.Context, query CheckEventAdminCodeQuery) error {
|
||||
conn := s.pool.Get(ctx)
|
||||
defer s.pool.Put(conn)
|
||||
|
||||
const dbQuery = `
|
||||
SELECT 1
|
||||
FROM event
|
||||
WHERE id = ? AND admin_code = ?;`
|
||||
var doesMatch bool
|
||||
err := sqlitex.Exec(conn, dbQuery,
|
||||
func(stmt *sqlite.Stmt) error {
|
||||
doesMatch = true
|
||||
return nil
|
||||
}, query.EventID, query.AdminCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !doesMatch {
|
||||
return UnauthorizedError{
|
||||
EventID: query.EventID,
|
||||
AdminCode: query.AdminCode,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetEventMetadataQuery struct {
|
||||
EventID string
|
||||
}
|
||||
|
@ -175,6 +175,7 @@ func TestGetEventResponseSummary(t *testing.T) {
|
||||
func TestCreateEventResponse(t *testing.T) {
|
||||
store, err := back.NewMemoryStore(back.SecureGenString)
|
||||
is.New(t).NoErr(err)
|
||||
defer store.Close()
|
||||
|
||||
createEvent := func(is *is.I) (eventID string) {
|
||||
event, err := store.CreateEvent(context.Background(), back.CreateEventCommand{
|
||||
@ -238,3 +239,64 @@ func TestCreateEventResponse(t *testing.T) {
|
||||
is.Equal(dateHours, response.DateHours)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeEventAdmin(t *testing.T) {
|
||||
store, err := back.NewMemoryStore(back.SecureGenString)
|
||||
is.New(t).NoErr(err)
|
||||
defer store.Close()
|
||||
|
||||
t.Run("returns ErrUnauthorized if admin code is wrong", func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
event, err := store.CreateEvent(context.Background(), back.CreateEventCommand{
|
||||
Name: "my event",
|
||||
Description: "stuff happening",
|
||||
Earliest: date.Today(),
|
||||
Latest: date.Today(),
|
||||
})
|
||||
is.NoErr(err)
|
||||
|
||||
badAdminCode := event.AdminCode + "x"
|
||||
err = store.AuthorizeEventAdmin(context.Background(), back.CheckEventAdminCodeQuery{
|
||||
EventID: event.EventID,
|
||||
AdminCode: badAdminCode,
|
||||
})
|
||||
is.Equal(err, back.UnauthorizedError{
|
||||
EventID: event.EventID,
|
||||
AdminCode: badAdminCode,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("return ErrUnauthorized if event does not exist", func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
randString, err := back.SecureGenString(10)
|
||||
is.NoErr(err)
|
||||
err = store.AuthorizeEventAdmin(context.Background(), back.CheckEventAdminCodeQuery{
|
||||
EventID: randString,
|
||||
AdminCode: randString,
|
||||
})
|
||||
is.Equal(err, back.UnauthorizedError{
|
||||
EventID: randString,
|
||||
AdminCode: randString,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("returns nil if admin code is correct", func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
event, err := store.CreateEvent(context.Background(), back.CreateEventCommand{
|
||||
Name: "my event",
|
||||
Description: "stuff happening",
|
||||
Earliest: date.Today(),
|
||||
Latest: date.Today(),
|
||||
})
|
||||
is.NoErr(err)
|
||||
|
||||
err = store.AuthorizeEventAdmin(context.Background(), back.CheckEventAdminCodeQuery{
|
||||
EventID: event.EventID,
|
||||
AdminCode: event.AdminCode,
|
||||
})
|
||||
is.NoErr(err)
|
||||
})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package front
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@ -300,7 +301,28 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, pathCreateSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *handler) blockUnauthorizedAdmin(w http.ResponseWriter, r *http.Request) bool {
|
||||
eventID := r.URL.Query().Get(fieldNameEventID)
|
||||
adminCode := r.URL.Query().Get(fieldNameAdminCode)
|
||||
|
||||
err := h.store.AuthorizeEventAdmin(context.Background(), back.CheckEventAdminCodeQuery{
|
||||
EventID: eventID,
|
||||
AdminCode: adminCode,
|
||||
})
|
||||
var authError back.UnauthorizedError
|
||||
if errors.As(err, &authError) {
|
||||
http.Error(w, "Event not found", http.StatusNotFound)
|
||||
logError(authError)
|
||||
return true
|
||||
}
|
||||
return internalServerError(w, err)
|
||||
}
|
||||
|
||||
func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
|
||||
if h.blockUnauthorizedAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
eventID := r.URL.Query().Get(fieldNameEventID)
|
||||
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
|
||||
EventID: eventID,
|
||||
@ -348,8 +370,13 @@ func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
if h.blockUnauthorizedAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
eventID := query.Get(fieldNameEventID)
|
||||
|
||||
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
||||
EventID: eventID,
|
||||
})
|
||||
|
8
todo.txt
8
todo.txt
@ -1,11 +1,12 @@
|
||||
Essential:
|
||||
------------
|
||||
Show response after submission
|
||||
Make earliest and latest dates required for creation
|
||||
Require earliest and latest dates for creation
|
||||
Ensure latest date is at least earliest date
|
||||
Ensure date span is within a maximum
|
||||
Add timeout to request context
|
||||
Prevent blank event names and guest names
|
||||
Consider some front-end, regex-based field validation
|
||||
Authenticate admin page with admin code
|
||||
It might be sufficient to require that when getting the response summary.
|
||||
Consider redirecting from /create/do to admin
|
||||
|
||||
Cleanup:
|
||||
@ -24,4 +25,5 @@ Make the sqlite pool size configurable
|
||||
|
||||
More features:
|
||||
---------------
|
||||
Show validation errors in a header, rather than on their own page
|
||||
Allow updating metadata on admin page
|
||||
|
Loading…
Reference in New Issue
Block a user