http: session/login reject disabled users; mid-session disable kicks immediately
This commit is contained in:
@@ -59,6 +59,9 @@ func (s *Server) authenticateAndSession(w stdhttp.ResponseWriter, r *stdhttp.Req
|
|||||||
if err := auth.VerifyPassword(u.PasswordHash, password); err != nil {
|
if err := auth.VerifyPassword(u.PasswordHash, password); err != nil {
|
||||||
return nil, errInvalidCredentials
|
return nil, errInvalidCredentials
|
||||||
}
|
}
|
||||||
|
if u.DisabledAt != nil {
|
||||||
|
return nil, errInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
token, err := auth.NewToken()
|
token, err := auth.NewToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ func (s *Server) requireUser(r *stdhttp.Request) (*store.User, bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
if u.DisabledAt != nil {
|
||||||
|
// Disabled mid-session — kill the session and reject the
|
||||||
|
// request as if it were unauthenticated.
|
||||||
|
_ = s.deps.Store.DeleteSession(r.Context(), auth.HashToken(c.Value))
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
return u, true
|
return u, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
stdhttp "net/http"
|
stdhttp "net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
|
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
|
||||||
)
|
)
|
||||||
@@ -95,6 +98,50 @@ func TestRequireRoleUnauthenticated401OnAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequireRoleRejectsDisabledMidSession(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
srv, urlBase := newTestServer(t, false)
|
||||||
|
uid := makeUser(t, srv, "victim", store.RoleOperator)
|
||||||
|
cookie := loginAs(t, srv, uid)
|
||||||
|
|
||||||
|
// Disable the user *while their session is still valid*.
|
||||||
|
if err := srv.deps.Store.DisableUser(t.Context(), uid, time.Now().UTC()); err != nil {
|
||||||
|
t.Fatalf("disable: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := stdhttp.NewRequest("GET", urlBase+"/api/hosts", nil)
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
res, err := stdhttp.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GET: %v", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != stdhttp.StatusUnauthorized {
|
||||||
|
t.Errorf("status: got %d want 401", res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginRejectsDisabledUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
srv, urlBase := newTestServer(t, false)
|
||||||
|
uid := makeUser(t, srv, "disabled1", store.RoleOperator)
|
||||||
|
if err := srv.deps.Store.DisableUser(t.Context(), uid, time.Now().UTC()); err != nil {
|
||||||
|
t.Fatalf("disable: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]string{
|
||||||
|
"username": "disabled1", "password": "test-password",
|
||||||
|
})
|
||||||
|
res, err := stdhttp.Post(urlBase+"/api/auth/login", "application/json", bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("POST: %v", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != stdhttp.StatusUnauthorized {
|
||||||
|
t.Errorf("status: got %d want 401", res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminBandRejectsOperator(t *testing.T) {
|
func TestAdminBandRejectsOperator(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// This test will start asserting 403 once Task B4 mounts /api/users
|
// This test will start asserting 403 once Task B4 mounts /api/users
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ func (s *Server) sessionUser(r *stdhttp.Request) (*ui.User, error) {
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if u.DisabledAt != nil {
|
||||||
|
_ = s.deps.Store.DeleteSession(r.Context(), auth.HashToken(c.Value))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return &ui.User{ID: u.ID, Username: u.Username, Role: string(u.Role)}, nil
|
return &ui.User{ID: u.ID, Username: u.Username, Role: string(u.Role)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user