Files
RenewCMS/features/user_roles.feature

115 lines
6.2 KiB
Gherkin

Feature: Users have a role, owner or admin, and only the owner can invite
As the person who registered the workspace
I want my account to be the owner and everyone I invite to be an admin
So that only I can invite people, while every admin keeps the same access to articles,
the users list, their own profile and Settings
Background:
Given a user has a role that is exactly "owner" or "admin", stored on the user record and read
from the user repository on every request, never from the jwt claims
And the first registered user is the owner: CreateUser creates the user with role "owner",
and registration is only possible while no verified user exists
And every user created by accepting an invite is an admin: AcceptInvite creates the user with
role "admin", whatever the inviter's role
And there is exactly one owner and no endpoint changes, transfers or removes a role
And AutoMigrate adds the role column to existing databases and backfills it: the user with the
lowest id becomes "owner" and every other user "admin"; a database that already has roles
is left unchanged by a later start
And the public user shape (api/pkg/publicuser) is id, username, email, is_verified, created_at,
updated_at, display_name and role
And the users list user items are id, kind, username, display_name, email, role, status,
created_at; invite items are unchanged: id, kind, email, status, created_at
And POST /v1/user/invite is owner-only: after the jwt, current-token and verified checks, a
RequireOwner check loads the user with GetUserUseCase and refuses any role other than
"owner" with 403 and the plain-text body apperr.OwnerRequiredErrorMessage
"Only the owner can do this.", before the rate limiter, the body decoding and the validation
And GET /v1/user/ stays open to every verified user, owner and admin alike
And error responses are plain text from http.Error, success responses are JSON
Scenario: The first registered user is the owner
Given the user repository is empty
When the client sends POST /v1/auth/register with username "marta", password "hunter2example12",
email "marta@renew.example"
Then the response status is 200
And the user repository holds one user with username "marta" and role "owner"
Scenario: A user created by accepting an invite is an admin
Given a verified owner "marta" and a pending invite for "lea.martin@renew.example" with a known raw code
When the client sends POST /v1/auth/invite/accept with that code, username "lea" and password "limewash2026"
Then the response status is 200
And the user repository holds user "lea" with role "admin"
And user "marta" still has role "owner"
Scenario Outline: GET /v1/auth/me returns the role of the session user
Given a verified user "<username>" with role "<role>" and a valid jwt cookie for that user
When the client sends GET /v1/auth/me
Then the response status is 200
And the response body has the key role with value "<role>"
Examples:
| username | role |
| marta | owner |
| lea | admin |
Scenario: An unverified owner sees their role on /auth/me
Given an unverified user "bob" with role "owner" and a valid jwt cookie
When the client sends GET /v1/auth/me
Then the response status is 200
And the response body's role is "owner" and is_verified is false
Scenario: The users list carries email and role on accepted users
Given verified owner id 1 "marta" with email "marta@renew.example"
And admin id 2 "jonas" with email "jonas@renew.example"
And admin id 3 "ines" with display_name "Inès Duarte" and email "ines.duarte@renew.example"
And a pending invite for "sam.oliveira@renew.example"
When the client sends GET /v1/user/ with a valid jwt cookie for user id 2
Then the response status is 200
And the user items have exactly the keys id, kind, username, display_name, email, role, status, created_at
And user 1 has email "marta@renew.example" and role "owner"; users 2 and 3 have role "admin" and their own email
And the invite item has exactly the keys id, kind, email, status, created_at
And no item has a key named password, password_reset_code, verification_code,
verification_expiration, password_reset_expiration, token_version, code, hashed_code,
invited_by or accepted_at
Scenario: The owner can invite
Given a verified owner id 1 "marta" and a valid jwt cookie for user id 1
When the client sends POST /v1/user/invite with body {"email": "sam.oliveira@renew.example"}
Then the response status is 201
And one invite exists and one "invitation" email was sent
Scenario: An admin cannot invite
Given a verified admin id 2 "jonas" and a valid jwt cookie for user id 2
When the client sends POST /v1/user/invite with body {"email": "sam.oliveira@renew.example"}
Then the response status is 403
And the response body is exactly apperr.OwnerRequiredErrorMessage, as plain text
And the invite repository is still empty
And no email was sent
Scenario Outline: The owner check comes before validation and the rate limiter
Given a verified admin id 2 and a valid jwt cookie for user id 2
When the client sends POST /v1/user/invite with body <body>
Then the response status is 403
And no invite was created and no email was sent
Examples:
| body |
| {"email": "not-an-email"} |
| not json |
Scenario: An admin's refused invites do not use up the rate limit
Given AUTH_RATE_LIMIT_MAX_ATTEMPTS is 2 for a window covering the whole test
When an admin at IP "203.0.113.20" sends 3 POST /v1/user/invite requests
Then all 3 responses are 403 and none is 429
Scenario: The role is read on every request, not from the token
Given a verified user id 2 whose stored role is "owner", with a valid jwt cookie
And the stored role changes to "admin" without the token changing
When the client sends POST /v1/user/invite with a valid body
Then the response status is 403
Scenario: An admin can still list users
Given a verified admin id 2 and a valid jwt cookie for user id 2
When the client sends GET /v1/user/
Then the response status is 200