Files
RenewCMS/features/login_screen.feature
T

123 lines
7.2 KiB
Gherkin

Feature: Log in screen
As a returning RenewCMS user
I want a /login screen that authenticates me against POST /v1/auth/login, shows why a previous
attempt or session failed, and offers to create the owner account only while registration is
open
So that I can get back into the back office from any entry point (a direct visit, an expired
session, a completed password reset) with copy that matches what actually happened
Background:
Given /login is a public route (meta.auth "public") rendered as LoginPage inside AuthShell,
per docs/design/routes.md
And on mount, LoginPage calls GET /v1/auth/registration to learn whether registration is open
And the form has a Username field (placeholder "Your username") and a Password field (a
password input with a show/hide toggle, autocomplete "current-password", and a
"Forgot password?" link), per docs/design/components.md
And client-side validation runs on submit before any request is sent: Username is required
with "Use at least 3 characters." under 3 characters, and Password is required with
"Enter your password." when empty
And a valid submit calls POST /v1/auth/login with { username, password }
And on a 200 response, LoginPage re-resolves the session (GET /v1/auth/me, per
features/nuxt_ui_shell_foundation.feature and features/api_client_global_error_handling.feature)
and then navigates: to the original ?redirect= target if present, else to /home when the
session is verified, or to /register/pending when it is not
And a 403 response means the username/password pair is wrong, since the API never reveals
which field was wrong
And a 429 response carries a Retry-After header in seconds, surfaced by useApi and displayed as
a live countdown on the submit button; the button re-enables when the countdown reaches zero
And a 500 response, or a request that never reaches the server, is surfaced as a network/server
error; the fields keep whatever the user typed
Scenario: Default — registration is closed
Given GET /v1/auth/registration resolves 200 with { "open": false }
When the user navigates to /login
Then the heading is "Log in"
And the description is "Welcome back. Log in to manage your articles."
And the Username field shows placeholder "Your username" with no value
And the Password field shows no value
And the submit button reads "Log in"
And no "Create the owner account" footer link is shown
Scenario: Fresh install — registration is open
Given GET /v1/auth/registration resolves 200 with { "open": true }
When the user navigates to /login
Then the heading, description and fields render exactly as in the Default state
And the footer reads "First time here?" with a "Create the owner account" link
Scenario: Validation errors — nothing is sent for a short username and an empty password
Given the user is on /login
When the user types "ma" into Username, leaves Password empty, and submits
Then POST /v1/auth/login is never called
And the Username field shows the error "Use at least 3 characters."
And the Password field shows the error "Enter your password."
Scenario: Submitting — fields and button are disabled while the request is in flight
Given the user is on /login with Username "marta" and Password "limewash2026"
When the user submits and POST /v1/auth/login has not yet resolved
Then the Username and Password fields are disabled
And the submit button reads "Logging in…" and shows a loading state
Scenario: Wrong credentials — the password is cleared and focused
Given the user is on /login with Username "marta" and Password "wrong-password"
When the user submits and POST /v1/auth/login resolves 403
Then an error alert reads "That username and password don't match" with description
"Check both and try again. Passwords are case-sensitive."
And the Username field still shows "marta"
And the Password field is cleared and receives focus
And the submit button reads "Log in" again, no longer loading
Scenario: Too many attempts — the button is disabled with a live countdown
Given the user is on /login with Username "marta" and Password "limewash"
When the user submits and POST /v1/auth/login resolves 429 with a Retry-After of 42 seconds
Then an info alert reads "Too many attempts" with description
"For your security, wait a moment before trying again."
And the Username field still shows "marta" and the Password field still shows "limewash"
And the submit button reads "Try again in 0:42" and is disabled
And the Username and Password fields are disabled while the countdown runs, as the board draws
them
When 42 seconds elapse
Then the submit button re-enables and reads "Log in"
And the Username and Password fields are editable again
Scenario: Session expired — redirected from a 401 elsewhere
Given the user is redirected to "/login?redirect=/article/12/edit&reason=expired"
When /login renders
Then an info alert reads "Your session expired" with description
"Log in again to pick up where you left off. Unsaved changes are kept on this device."
And the Password field receives focus
And the submit button reads "Log in"
Scenario: Password changed — arrived from a successful reset
Given the user is redirected to "/login?reason=password-changed"
When /login renders
Then a success alert reads "Password changed" with description "Log in with your new password."
And the Username field shows placeholder "Your username" with no value
And the submit button reads "Log in"
Scenario Outline: Server or network error — the fields keep their values
Given the user is on /login with Username "marta" and Password <password>
When the user submits and POST /v1/auth/login <failure>
Then an error alert reads "Couldn't reach RenewCMS" with description
"Check your connection and try again."
And the Username field still shows "marta" and the Password field still shows <password>
And the submit button reads "Log in" again, no longer loading
Examples:
| password | failure |
| "limewash2026" | fails with a 500 response |
| "limewash2026" | never reaches the server |
| "short1" | fails with a 400 response, because "short1" clears client-side validation (non-empty, 3+ characters) but is under the server's 8-character minimum, which the client does not check |
Scenario Outline: A successful log in navigates to the right destination
Given the user is on <path>
And GET /v1/auth/me, called after the 200 response, resolves 200 with is_verified <verified>
When the user submits valid credentials and POST /v1/auth/login resolves 200 with a jwt cookie
Then the app navigates to <destination>
Examples:
| path | verified | destination |
| /login | true | /home |
| /login | false | /register/pending |
| /login?redirect=/article/12/edit | true | /article/12/edit |