Feature: Registration is only open while no verified account exists, and it sends a time-limited verification email As the maintainer of RenewCMS I want the "is registration still open" rule enforced by the create-user use case instead of the auth handler, and a verification email sent on every successful registration whose link uses the configured public URL and whose code expires two hours after creation So that the business rule is testable independently of HTTP, and a newly registered user always receives a working, time-limited verification link built from the server's own configuration Background: Given POST /v1/auth/register is public, decodes RegisterCredentials (username, password, email) and validates them with validator.Validate.Struct before anything else And today, the gate lives in api/handlers/auth.Handler.SomeUsersVerified, which calls ListUsersUseCase.ListUsers and loops over IsVerified before the body is even decoded; this slice moves that check into CreateUserUseCase.CreateUser, which consults user.Repository.GetAll itself and returns the sentinel user.ErrRegistrationClosed when at least one existing user already has IsVerified true And the handler stops performing the check itself and stops calling ListUsersUseCase for this purpose: it calls CreateUserUseCase.CreateUser unconditionally and maps user.ErrRegistrationClosed to a 403 response with the same text it returns today, "You are not allowed to create a user. Log in or reset database.", now defined as a named constant in api/pkg/apperr (e.g. apperr.RegistrationClosedErrorMessage) instead of an inline literal in the handler And CreateUserUseCase.CreateUser already sets VerificationExpiration to time.Now().Add(2 * time.Hour) and returns the raw (unhashed) verification code while storing only its hash; this slice does not change that expiry computation And the email send moves into CreateUserUseCase itself: NewCreateUserUseCase gains a mail.Repository (the existing domain interface) and the configured public URL string as constructor parameters, both injected from config.Settings.PublicURL in api/dependecyInjection.go; the handler no longer sends mail and no longer holds a SendMailUseCase or PublicURL itself And after user.Repository.Create succeeds, CreateUserUseCase.CreateUser calls mail.Repository.Send with the user's email, template "verification" (internal/infrastructure/mailer/templates/verification.html, which renders a link from "{{.Host}}/register/validate?c={{.VerificationCode}}"), and data whose Host is the injected public URL and whose VerificationCode is the raw (unhashed) code And if mail.Repository.Send returns an error, CreateUserUseCase.CreateUser deletes the just-created user through user.Repository.Delete and returns an error wrapping the send failure, so no half-registered user is left behind and the same username and email can be used to register again And the handler maps that CreateUser error to a 500 response and never sets the jwt cookie in that case, since CreateUser itself never returned a created user Scenario: Registration succeeds when no verified user exists and sends the verification email Given the user repository is empty 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 exactly one email was sent to "alice@example.com" using template "verification" And that email's data has VerificationCode equal to the raw code returned by CreateUser, not its stored hash Scenario: The verification email's Host reflects the injected public URL Given a config.Settings value with PublicURL "https://cms.example.com" And the registration flow is built from that settings value When the client sends POST /v1/auth/register with username "alice", password "hunter2example12", email "alice@example.com" Then the email sent to "alice@example.com" has data.Host equal to "https://cms.example.com" And changing the process environment variable PUBLIC_URL afterward does not change that value on a second registration built from the same settings value Scenario: The stored verification code expires two hours after creation Given the user repository is empty When the client sends POST /v1/auth/register with username "alice", password "hunter2example12", email "alice@example.com" Then the created user's VerificationExpiration is within 5 seconds of time.Now().Add(2 * time.Hour) Scenario: Registration is rejected once a verified user exists, and no email is sent Given the user repository contains a user "alice" with IsVerified true When the client sends POST /v1/auth/register with username "bob", password "hunter2example12", email "bob@example.com" Then the response status is 403 And the response body is "You are not allowed to create a user. Log in or reset database." And the user repository still contains only "alice" And no email was sent Scenario: A failed verification email rolls back the just-created user Given the user repository is empty And the mail repository is set to fail on its next Send call When the client sends POST /v1/auth/register with username "alice", password "hunter2example12", email "alice@example.com" Then the response status is 500 And no jwt cookie is set And the user repository no longer contains a user named "alice" When the client then sends POST /v1/auth/register again with username "alice", password "hunter2example12", email "alice@example.com", and the mail repository now succeeds Then the response status is 200 And a jwt cookie is set Scenario: The create-user use case itself enforces the gate, independently of any handler Given a user.Repository fake containing one user with IsVerified true And a CreateUserUseCase built directly over that fake, a hasher and a mail.Repository fake When CreateUser is called with username "bob", password "hunter2example12", email "bob@example.com" Then the call returns user.ErrRegistrationClosed, asserted with errors.Is And the fake repository's Create method was never invoked And no email was sent Scenario: The create-user use case allows registration while every existing user is unverified Given a user.Repository fake containing two users, both with IsVerified false And a CreateUserUseCase built directly over that fake, a hasher and a mail.Repository fake When CreateUser is called with username "carol", password "hunter2example12", email "carol@example.com" Then the call succeeds and returns a user with IsVerified false And exactly one email was sent to "carol@example.com" using template "verification" Scenario: The create-user use case rolls back the user when the mail repository fails Given an empty user.Repository fake And a CreateUserUseCase built directly over that fake, a hasher and a mail.Repository fake set to fail on its next Send call When CreateUser is called with username "alice", password "hunter2example12", email "alice@example.com" Then the call returns an error wrapping the mail repository's failure And the fake repository no longer contains a user named "alice"