113 lines
6.8 KiB
Gherkin
113 lines
6.8 KiB
Gherkin
Feature: A logged-in, unverified user cancels their pending registration
|
|
|
|
As the maintainer of RenewCMS
|
|
I want a logged-in user who has not yet verified their account to be able to delete that
|
|
account and clear their session in one call, while a user who is already verified cannot use
|
|
this route to delete their own account, and a token that carries no usable user_id is answered
|
|
with 401 instead of a panic
|
|
So that someone who registered by mistake, or changed their mind before verifying, can free their
|
|
username and email immediately (features/unverified_users_and_reregistration.feature), without
|
|
exposing a general self-delete for verified accounts
|
|
|
|
Background:
|
|
Given this slice adds DELETE /v1/auth/register, mounted in api/router.go inside the same
|
|
protected group NewAuthRouter already applies to POST /v1/auth/verify: jwtauth.Verifier then
|
|
jwtauth.Authenticator, so a request with no jwt cookie, an expired jwt, or a jwt with a bad
|
|
signature never reaches the handler and is answered 401 by jwtauth.Authenticator itself
|
|
And the handler extracts user_id from the already-verified token's claims using the same safe
|
|
pattern auth.Handler.Verify already established (features/account_verification.feature):
|
|
401 with apperr.UnauthorizedErrorMessage whenever the "user_id" claim is missing, is not a
|
|
number, or does not match any user in the repository, never a panic and never trusting the
|
|
raw claim value
|
|
And a new use case, CancelRegistrationUseCase (internal/infrastructure/useCases/CancelRegistration.go),
|
|
is injected into auth.Handler as CancelRegistrationUseCase; it depends only on user.Repository
|
|
and exposes CancelRegistration(userId uint32) error
|
|
And CancelRegistrationUseCase.CancelRegistration loads the user by id, returns the existing
|
|
sentinel user.ErrUserNotFound when no such user exists, refuses with a new sentinel
|
|
user.ErrAlreadyVerified when the loaded user's IsVerified is already true, and otherwise calls
|
|
user.Repository.Delete(userId), whose GORM implementation removes the row entirely rather than
|
|
soft-deleting it (features/unverified_users_and_reregistration.feature), so the same username
|
|
and email are immediately free for a new registration
|
|
And the handler maps user.ErrUserNotFound to 401 with apperr.UnauthorizedErrorMessage, the same
|
|
status and body Verify already uses for an unresolvable user_id, and maps
|
|
user.ErrAlreadyVerified to 403 with a new apperr.AlreadyVerifiedErrorMessage constant
|
|
And on success the handler clears the "jwt" cookie exactly the way auth.Handler.Logout already
|
|
does (Max-Age -1, empty value, Path "/") and responds 200 with {"message": "..."}
|
|
And on any error path the handler returns before touching the "jwt" cookie, so an unresolved or
|
|
already-verified request leaves the caller's existing session untouched
|
|
|
|
Scenario: An unverified user cancels their pending registration
|
|
Given a user repository containing user id 7, username "alice", email "alice@example.com",
|
|
is_verified false
|
|
And a valid jwt cookie for user id 7
|
|
When the client sends DELETE /v1/auth/register with that cookie
|
|
Then the response status is 200
|
|
And the response body is {"message": "..."}
|
|
And the response clears the "jwt" cookie
|
|
And the user repository no longer contains user id 7; Get(7) returns user.ErrUserNotFound
|
|
|
|
Scenario: Cancelling frees the username and email for a new registration
|
|
Given a user repository containing user id 7, username "alice", email "alice@example.com",
|
|
is_verified false
|
|
And a valid jwt cookie for user id 7
|
|
When the client sends DELETE /v1/auth/register with that cookie
|
|
Then the response status is 200
|
|
When the client sends POST /v1/auth/register with username "alice", password
|
|
"hunter2example12", email "alice@example.com"
|
|
Then the response status is 200
|
|
And a jwt cookie is set
|
|
And the newly created user's id is different from 7
|
|
|
|
Scenario: An already-verified user cannot cancel their account this way
|
|
Given a user repository containing user id 7, username "alice", email "alice@example.com",
|
|
is_verified true
|
|
And a valid jwt cookie for user id 7
|
|
When the client sends DELETE /v1/auth/register with that cookie
|
|
Then the response status is 403
|
|
And the response body is the new apperr.AlreadyVerifiedErrorMessage text
|
|
And the response does not clear the "jwt" cookie
|
|
And the user repository's record for id 7 is unchanged: is_verified true, the row still exists
|
|
|
|
Scenario Outline: A token without a valid user_id returns 401 instead of crashing
|
|
Given a user repository containing user id 7, username "alice", is_verified false
|
|
And <token description>
|
|
When the client sends DELETE /v1/auth/register with that cookie
|
|
Then the response status is 401
|
|
And the response body is the apperr.UnauthorizedErrorMessage text
|
|
And the handler does not panic
|
|
And the response does not clear the "jwt" cookie
|
|
And the user repository's record for id 7 still exists
|
|
|
|
Examples:
|
|
| token description |
|
|
| a validly signed jwt cookie whose claims carry no "user_id" key at all |
|
|
| a validly signed jwt cookie whose "user_id" claim is the string "seven" |
|
|
| a validly signed jwt cookie whose "user_id" claim is the number 999, which does not match any user |
|
|
|
|
Scenario: A request with no jwt cookie never reaches the handler
|
|
Given a user repository containing user id 7, username "alice", is_verified false
|
|
And no jwt cookie is sent
|
|
When the client sends DELETE /v1/auth/register without a cookie
|
|
Then the response status is 401
|
|
And the user repository's record for id 7 still exists
|
|
|
|
Scenario: The use case deletes an unverified user, independently of any handler
|
|
Given a user.Repository fake containing user id 3, is_verified false
|
|
And a CancelRegistrationUseCase built directly over that fake
|
|
When CancelRegistration is called with userId 3
|
|
Then the call returns nil
|
|
And the fake repository no longer contains user id 3
|
|
|
|
Scenario: The use case refuses to delete an already-verified user, independently of any handler
|
|
Given a user.Repository fake containing user id 3, is_verified true
|
|
And a CancelRegistrationUseCase built directly over that fake
|
|
When CancelRegistration is called with userId 3
|
|
Then the call returns user.ErrAlreadyVerified, asserted with errors.Is
|
|
And the fake repository still contains user id 3
|
|
|
|
Scenario: The use case reports an unknown user, independently of any handler
|
|
Given a user.Repository fake containing no user with id 42
|
|
And a CancelRegistrationUseCase built directly over that fake
|
|
When CancelRegistration is called with userId 42
|
|
Then the call returns user.ErrUserNotFound, asserted with errors.Is
|