115 lines
6.2 KiB
Gherkin
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
|