0.3.22 - PINs
This commit is contained in:
@@ -23,7 +23,7 @@ import (
|
||||
// since been unplugged.
|
||||
|
||||
type adminViewersResponse struct {
|
||||
Viewers []store.Viewer `json:"viewers"`
|
||||
Viewers []adminViewer `json:"viewers"`
|
||||
// Whether the household's own switch is on. The page says so rather than quietly
|
||||
// offering controls whose effect nothing on any television would show: an operator who
|
||||
// has switched viewers off and then adds one has done something that looks like it
|
||||
@@ -34,6 +34,11 @@ type adminViewersResponse struct {
|
||||
MaxShadowViewers int `json:"maxShadowViewers"`
|
||||
}
|
||||
|
||||
type adminViewer struct {
|
||||
store.Viewer
|
||||
PIN string `json:"pin,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminViewers(w http.ResponseWriter, r *http.Request) {
|
||||
userID := strings.TrimSpace(r.PathValue("userID"))
|
||||
if userID == "" {
|
||||
@@ -53,8 +58,17 @@ func (s *Server) handleAdminViewers(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusInternalServerError, "could not load viewers")
|
||||
return
|
||||
}
|
||||
admin := make([]adminViewer, 0, len(viewers))
|
||||
for _, viewer := range viewers {
|
||||
pin, pinErr := s.store.ViewerPIN(r.Context(), userID, viewer.ID)
|
||||
if pinErr != nil {
|
||||
writeError(w, http.StatusInternalServerError, "could not load PINs")
|
||||
return
|
||||
}
|
||||
admin = append(admin, adminViewer{Viewer: viewer, PIN: pin})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, adminViewersResponse{
|
||||
Viewers: viewers,
|
||||
Viewers: admin,
|
||||
Enabled: s.viewersEnabled(r.Context()),
|
||||
MaxShadowViewers: store.MaxShadowViewers,
|
||||
})
|
||||
@@ -70,11 +84,31 @@ func (s *Server) handleAdminCreateViewer(w http.ResponseWriter, r *http.Request,
|
||||
writeError(w, http.StatusBadRequest, "a name of up to 40 characters is required")
|
||||
return
|
||||
}
|
||||
var requestedPINHash []byte
|
||||
if req.PIN != "" {
|
||||
var err error
|
||||
requestedPINHash, err = pinHash(req.PIN)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
viewer, err := s.store.CreateShadowViewer(r.Context(), userID, req.Name, req.ShortName, req.Colour)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if req.PIN != "" {
|
||||
if err := s.store.SetViewerPIN(r.Context(), userID, viewer.ID, requestedPINHash); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "could not save PIN")
|
||||
return
|
||||
}
|
||||
if err := s.store.SetViewerPINValue(r.Context(), userID, viewer.ID, req.PIN); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "could not save PIN")
|
||||
return
|
||||
}
|
||||
viewer.HasPIN = true
|
||||
}
|
||||
// The televisions hold a cached list for viewerListTTL, so the write clears it here for
|
||||
// the same reason it does on the client-facing route: a person added from the console
|
||||
// must be pickable on the next request rather than at the end of the window.
|
||||
@@ -117,9 +151,17 @@ func (s *Server) handleAdminViewer(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "malformed request body")
|
||||
return
|
||||
}
|
||||
viewer, err := s.store.UpdateShadowViewer(
|
||||
r.Context(), userID, viewerID, req.Name, req.ShortName, req.Colour,
|
||||
)
|
||||
var viewer store.Viewer
|
||||
var err error
|
||||
// The main viewer cannot be renamed here, but its PIN is an account credential and
|
||||
// administrators must still be able to set or reset it.
|
||||
if viewerID == userID && (req.PIN != "" || req.ClearPIN) {
|
||||
viewer, err = s.store.ViewerFor(r.Context(), userID, viewerID)
|
||||
} else {
|
||||
viewer, err = s.store.UpdateShadowViewer(
|
||||
r.Context(), userID, viewerID, req.Name, req.ShortName, req.Colour,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrViewerNotFound) {
|
||||
// The main viewer lands here too, and that is the honest answer: its name is
|
||||
@@ -130,6 +172,28 @@ func (s *Server) handleAdminViewer(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if req.ClearPIN || req.PIN != "" {
|
||||
if req.ClearPIN {
|
||||
err = s.store.ClearViewerPIN(r.Context(), userID, viewerID)
|
||||
if err == nil {
|
||||
err = s.store.SetViewerPINValue(r.Context(), userID, viewerID, "")
|
||||
}
|
||||
} else {
|
||||
var hash []byte
|
||||
hash, err = pinHash(req.PIN)
|
||||
if err == nil {
|
||||
err = s.store.SetViewerPIN(r.Context(), userID, viewerID, hash)
|
||||
if err == nil {
|
||||
err = s.store.SetViewerPINValue(r.Context(), userID, viewerID, req.PIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
viewer.HasPIN = !req.ClearPIN
|
||||
}
|
||||
s.forgetViewers(userID)
|
||||
s.loggerFor(r.Context()).Info("viewer renamed by operator",
|
||||
"account", userID, "viewer", viewer.ID, "name", viewer.Name)
|
||||
|
||||
Reference in New Issue
Block a user