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 "" with 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 "" 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 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