Restrict platform-list edits to admins
Anyone authenticated can still read the shared platform list, but only an
admin may add, rename or delete from it.
- users.is_admin boolean, defaulting to false. The very first account ever
registered becomes an admin automatically (CreateUser's INSERT uses
NOT EXISTS (SELECT 1 FROM users) for is_admin), so there's always at
least one without manual setup; promoting anyone beyond that is a manual
UPDATE for now (documented in the migration's comment on the column).
- GetSessionAndUser, GetUserByEmail and GetUserByID now also select
is_admin, and it flows through to model.User (and so to
/api/auth/{register,login,me}'s response) and to the auth middleware's
authedUser.
- New requireAdminMiddleware + addAdminAuth, layered on top of the existing
auth middleware. Wired onto create/update/delete-platform; list/get stay
on plain addAuth, since reading the list only requires being logged in.
Verified end to end: the first registered user comes back is_admin:true,
the second false; the non-admin gets 403 on create/update/delete-platform
but 200 on list; the admin can do all of it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7fbd403d30
commit
384c4a7217
12 files changed
+110
-35
No files matched your search
@@ -43,6 +43,16 @@ func authedMock() *MockQuerier {
|
||||
}
|
||||
}
|
||||
|
||||
// adminMock is authedMock for a session belonging to an admin, for the
|
||||
// platform-mutation routes that require one.
|
||||
func adminMock() *MockQuerier {
|
||||
m := authedMock()
|
||||
m.GetSessionAndUserFunc = func(ctx context.Context, tokenHash []byte) (db.GetSessionAndUserRow, error) {
|
||||
return db.GetSessionAndUserRow{ID: testUserID, Email: "a@example.com", Name: "Alice", IsAdmin: true}, nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func authedRequest(method, path, body string) *http.Request {
|
||||
var req *http.Request
|
||||
if body == "" {
|
||||
@@ -118,7 +128,7 @@ func TestDeletePlatformConflictWhenReferencedByGames(t *testing.T) {
|
||||
// db/query.sql's DeletePlatform comment), so the handler has to notice
|
||||
// the 0-rows result itself and turn it into 409, not just pass through
|
||||
// whatever error (if any) the query returns.
|
||||
mockDB := authedMock()
|
||||
mockDB := adminMock()
|
||||
mockDB.GetPlatformFunc = func(ctx context.Context, id uuid.UUID) (db.GetPlatformRow, error) {
|
||||
return db.GetPlatformRow{ID: id, Name: "PC"}, nil
|
||||
}
|
||||
@@ -134,7 +144,7 @@ func TestDeletePlatformConflictWhenReferencedByGames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeletePlatformNotFound(t *testing.T) {
|
||||
mockDB := authedMock()
|
||||
mockDB := adminMock()
|
||||
mockDB.GetPlatformFunc = func(ctx context.Context, id uuid.UUID) (db.GetPlatformRow, error) {
|
||||
return db.GetPlatformRow{}, pgx.ErrNoRows
|
||||
}
|
||||
@@ -160,7 +170,7 @@ func TestCreateGameRejectsScoreOutOfRange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreatePlatform(t *testing.T) {
|
||||
mockDB := authedMock()
|
||||
mockDB := adminMock()
|
||||
mockDB.CreatePlatformFunc = func(ctx context.Context, name string) (db.CreatePlatformRow, error) {
|
||||
return db.CreatePlatformRow{ID: uuid.New(), Name: name}, nil
|
||||
}
|
||||
@@ -173,6 +183,18 @@ func TestCreatePlatform(t *testing.T) {
|
||||
assert.Contains(t, rec.Body.String(), "Sega Genesis")
|
||||
}
|
||||
|
||||
func TestCreatePlatformForbiddenForNonAdmin(t *testing.T) {
|
||||
mockDB := authedMock()
|
||||
// No CreatePlatformFunc set: the admin check must reject the request
|
||||
// before the handler ever calls the database, or this test would panic.
|
||||
|
||||
e, _ := newTestAPI(mockDB)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, authedRequest(http.MethodPost, "/api/platforms", `{"name": "Sega Genesis"}`))
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
}
|
||||
|
||||
func TestListPlatformsIsSharedAcrossUsers(t *testing.T) {
|
||||
// Unlike games, the platform list has no user_id to scope by: every
|
||||
// authenticated user sees the same rows. ListPlatformsFunc taking no
|
||||
|
||||
Reference in new issue
Block a user