91 lines
5.8 KiB
Gherkin
91 lines
5.8 KiB
Gherkin
Feature: A logged-in user fetches their own public profile without triggering the verified-account gate
|
|
|
|
As the maintainer of RenewCMS
|
|
I want the SPA to ask "who is currently logged in" via a single endpoint that answers from the
|
|
same jwt cookie every other protected route already trusts, distinguishes "no valid session"
|
|
from "a session for an unverified account" so it can route the user to a pending-verification
|
|
screen instead of guessing, and never competes with real login attempts for rate-limit headroom
|
|
So that the SPA can restore a session on load without going through the verification gate and
|
|
without being throttled like a login attempt
|
|
|
|
Background:
|
|
Given the public user struct and its converter, shared by the auth and user handlers from a
|
|
package under api/pkg (features/user_display_name.feature), exactly the JSON shape
|
|
POST /v1/auth/verify returns: id, username, email, is_verified, created_at, updated_at and
|
|
display_name
|
|
And this slice adds GET /v1/auth/me, handled by a new MeHandler (api/handlers/auth/meHandler.go)
|
|
built with NewMeHandler(container.GetUserUseCase), the same GetUserUseCase every other
|
|
protected handler already gets from api/dependecyInjection.go; no new use case or domain
|
|
method is introduced
|
|
And GET /v1/auth/me is mounted in api/router.go's NewAuthRouter inside the same group that
|
|
already carries jwtauth.Verifier(container.Token), jwtauth.Authenticator(container.Token) and
|
|
middleware.RequireCurrentToken(container.GetUserUseCase) ahead of /verify and
|
|
DELETE /register, so:
|
|
- no jwt cookie, or a jwt with a bad signature, is refused with 401 by jwtauth.Authenticator
|
|
itself before the handler runs, the same as the other routes in that group
|
|
(features/stale_token_invalidation.feature)
|
|
- a well-formed but stale token (whose token_version claim no longer matches the stored user,
|
|
per features/stale_token_invalidation.feature) is refused with 401
|
|
apperr.UnauthorizedErrorMessage by RequireCurrentToken before the handler runs
|
|
- a token minted for a user id that no longer exists (a cancelled registration,
|
|
features/cancel_pending_registration.feature) is refused with the same 401
|
|
apperr.UnauthorizedErrorMessage by RequireCurrentToken, for the same reason
|
|
And unlike NewArticleRouter's protected group, this group does not add
|
|
middleware.RequireVerifiedUser, and unlike /login and /password-reset, it does not add
|
|
middleware.NewRateLimiter, so an unverified user reaches the handler, and the route has no
|
|
per-client attempt ceiling
|
|
And MeHandler.Me resolves user_id with claims.UserId(r) (401 apperr.UnauthorizedErrorMessage if
|
|
somehow absent or malformed, though RequireCurrentToken already guarantees a valid one before
|
|
this handler is reached), loads the user with GetUserUseCase.GetUser(userId) (401
|
|
apperr.UnauthorizedErrorMessage on user.ErrUserNotFound, 500 "Internal server error" on any
|
|
other error), sets the response header Cache-Control: no-store, and responds 200 with
|
|
newPublicUser(loadedUser) as JSON, whatever that user's is_verified value is
|
|
|
|
Scenario: A verified user fetches their own profile
|
|
Given a user repository containing user id 7, username "alice", email "alice@example.com",
|
|
is_verified true, TokenVersion 0
|
|
And a valid jwt cookie for user id 7 encoding token_version 0
|
|
When the client sends GET /v1/auth/me with that cookie
|
|
Then the response status is 200
|
|
And the response header Cache-Control is "no-store"
|
|
And the response body is the user as JSON with id 7, username "alice", email
|
|
"alice@example.com", is_verified true
|
|
|
|
Scenario: An unverified user still fetches their own profile, with is_verified false, not a 403
|
|
Given a user repository containing user id 8, username "bob", email "bob@example.com",
|
|
is_verified false, TokenVersion 0
|
|
And a valid jwt cookie for user id 8 encoding token_version 0
|
|
When the client sends GET /v1/auth/me with that cookie
|
|
Then the response status is 200
|
|
And the response header Cache-Control is "no-store"
|
|
And the response body is the user as JSON with id 8, username "bob", email "bob@example.com",
|
|
is_verified false
|
|
|
|
Scenario: No jwt cookie at all is refused
|
|
Given a user repository containing user id 7, username "alice", is_verified true, TokenVersion 0
|
|
When the client sends GET /v1/auth/me with no jwt cookie
|
|
Then the response status is 401
|
|
|
|
Scenario: A jwt whose token_version no longer matches the stored user is refused
|
|
Given a user repository containing user id 7, username "alice", is_verified true, TokenVersion 1
|
|
And a jwt cookie for user id 7 encoding token_version 0, minted before the version advanced
|
|
When the client sends GET /v1/auth/me with that cookie
|
|
Then the response status is 401
|
|
And the response body is the apperr.UnauthorizedErrorMessage text
|
|
|
|
Scenario: A jwt minted for a since-deleted account is refused
|
|
Given a user repository containing user id 7, username "alice", is_verified false, TokenVersion 0
|
|
And a valid jwt cookie for user id 7 encoding token_version 0
|
|
When the client sends DELETE /v1/auth/register with that cookie
|
|
Then the response status is 200
|
|
When the client sends GET /v1/auth/me with the same cookie
|
|
Then the response status is 401
|
|
And the response body is the apperr.UnauthorizedErrorMessage text
|
|
|
|
Scenario: The route is never rate-limited, unlike login and password reset
|
|
Given a user repository containing user id 7, username "alice", is_verified true, TokenVersion 0
|
|
And a valid jwt cookie for user id 7 encoding token_version 0
|
|
When the client sends GET /v1/auth/me 5 times in immediate succession
|
|
Then every response status is 200
|
|
And none of the responses is 429
|