Check admin code for admin pages
This commit is contained in:
parent
3ad9b2955f
commit
4eca005046
@ -3,6 +3,7 @@ package back
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -13,6 +14,15 @@ import (
|
|||||||
|
|
||||||
var ErrNotFound = errors.New("not found")
|
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 GenString func(length int) (string, error)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
@ -164,6 +174,37 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
|
|||||||
return
|
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 {
|
type GetEventMetadataQuery struct {
|
||||||
EventID string
|
EventID string
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,7 @@ func TestGetEventResponseSummary(t *testing.T) {
|
|||||||
func TestCreateEventResponse(t *testing.T) {
|
func TestCreateEventResponse(t *testing.T) {
|
||||||
store, err := back.NewMemoryStore(back.SecureGenString)
|
store, err := back.NewMemoryStore(back.SecureGenString)
|
||||||
is.New(t).NoErr(err)
|
is.New(t).NoErr(err)
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
createEvent := func(is *is.I) (eventID string) {
|
createEvent := func(is *is.I) (eventID string) {
|
||||||
event, err := store.CreateEvent(context.Background(), back.CreateEventCommand{
|
event, err := store.CreateEvent(context.Background(), back.CreateEventCommand{
|
||||||
@ -238,3 +239,64 @@ func TestCreateEventResponse(t *testing.T) {
|
|||||||
is.Equal(dateHours, response.DateHours)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -300,7 +301,28 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, pathCreateSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
|
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) {
|
func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.blockUnauthorizedAdmin(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
eventID := r.URL.Query().Get(fieldNameEventID)
|
eventID := r.URL.Query().Get(fieldNameEventID)
|
||||||
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
|
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
|
||||||
EventID: eventID,
|
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) {
|
func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.blockUnauthorizedAdmin(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
eventID := query.Get(fieldNameEventID)
|
eventID := query.Get(fieldNameEventID)
|
||||||
|
|
||||||
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
})
|
})
|
||||||
|
8
todo.txt
8
todo.txt
@ -1,11 +1,12 @@
|
|||||||
Essential:
|
Essential:
|
||||||
------------
|
------------
|
||||||
Show response after submission
|
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
|
Prevent blank event names and guest names
|
||||||
Consider some front-end, regex-based field validation
|
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
|
Consider redirecting from /create/do to admin
|
||||||
|
|
||||||
Cleanup:
|
Cleanup:
|
||||||
@ -24,4 +25,5 @@ Make the sqlite pool size configurable
|
|||||||
|
|
||||||
More features:
|
More features:
|
||||||
---------------
|
---------------
|
||||||
|
Show validation errors in a header, rather than on their own page
|
||||||
Allow updating metadata on admin page
|
Allow updating metadata on admin page
|
||||||
|
Loading…
Reference in New Issue
Block a user