Files
RenewCMS/features/hasher_domain_interface.feature

93 lines
5.1 KiB
Gherkin

Feature: Password and code hashing go through a domain Hasher interface
As the maintainer of RenewCMS
I want password and verification/reset-code hashing done by a domain hash.Hasher
interface, used only by use cases
So that internal/infrastructure/persistence/repositories/userRepository.go and
api/handlers/auth/authHandler.go stop calling bcrypt directly, and a use case can be
tested against a fake hasher instead of a real bcrypt cost factor
Background:
Given internal/domain/hash defines the interface Hasher with Hash(value string) (string, error)
and Compare(hashed string, value string) error
And internal/domain/user defines the sentinel user.ErrInvalidCredentials alongside the
existing user.ErrUserNotFound
And internal/infrastructure/hashing.BcryptHasher implements hash.Hasher and is asserted
with var _ hash.Hasher = &BcryptHasher{}
And api/dependecyInjection.go constructs one BcryptHasher and passes it to every use case
that hashes or compares a secret: CreateUserUseCase, AuthenticateUserUseCase,
VerifyUserUseCase, RequestPasswordResetUseCase and ResetPasswordUseCase
And UserRepository.Create, UpdatePassword and UpdatePasswordResetCode store the string
they are given verbatim and never call bcrypt
And the auth Handler no longer imports golang.org/x/crypto/bcrypt; Login calls
AuthenticateUserUseCase.Authenticate instead of comparing a hash itself
Scenario: Creating a user stores a hashed password and a hashed verification code
Given a fake hasher that hashes any value "x" to "hashed:x"
When CreateUserUseCase.CreateUser is called with username "alice", password
"s3cretPassw0rd" and email "alice@example.com"
Then the repository's Create is called with a user whose Password is "hashed:s3cretPassw0rd"
And that user's VerificationCode is "hashed:<the generated code>", not the raw code
And the User returned by the use case has VerificationCode equal to the raw generated
code, not the hash
Scenario: A hashing failure while creating a user persists nothing
Given a fake hasher whose Hash always returns an error
When CreateUserUseCase.CreateUser is called with username "alice", password
"s3cretPassw0rd" and email "alice@example.com"
Then the use case returns that error
And the repository's Create is never called
Scenario: Authenticating with the correct password returns the user
Given a user repository containing a user "alice" with stored password "hashed:hunter2example"
And a fake hasher whose Compare returns nil when given "hashed:hunter2example" and "hunter2example"
When AuthenticateUserUseCase.Authenticate is called with username "alice" and password
"hunter2example"
Then the result is that user, with no error
Scenario: Authenticating with the wrong password returns a domain sentinel error
Given a user repository containing a user "alice" with stored password "hashed:hunter2example"
And a fake hasher whose Compare returns an error for that password
When AuthenticateUserUseCase.Authenticate is called with username "alice" and password
"wrongpassword12"
Then the result is the error user.ErrInvalidCredentials
And errors.Is(err, user.ErrInvalidCredentials) is true
Scenario: Authenticating an unknown username never calls the hasher
Given a user repository with no user "bob"
When AuthenticateUserUseCase.Authenticate is called with username "bob" and password
"hunter2example"
Then the result is the error user.ErrUserNotFound
And the hasher's Compare is never called
Scenario Outline: Login over HTTP still can't distinguish a bad password from an unknown username
Given a user repository containing a user "alice" with stored password "hashed:hunter2example"
And a real BcryptHasher
When the client sends POST /v1/auth/login with username "<username>" and password "<password>"
Then the response status is 403
And the response body is the text apperr.LogsErrorMessage
And no jwt cookie is set
Examples:
| username | password | case |
| alice | wrongpassword12 | existing user, bad pass |
| bob | hunter2example | unknown username |
Scenario: Registering a user stores the hashed password, never the plaintext
Given an empty user repository
And a real BcryptHasher
When the client sends POST /v1/auth/register with username "alice", password
"s3cretPassw0rd" and email "alice@example.com"
Then the response status is 200
And a jwt cookie is set
And the user repository's stored Password for "alice" is not "s3cretPassw0rd"
And BcryptHasher.Compare("<the stored value>", "s3cretPassw0rd") returns nil
Scenario: A hashing failure during registration surfaces as 500 and sets no cookie
Given an empty user repository
And a fake hasher whose Hash always returns an error
When the client sends POST /v1/auth/register with username "alice", password
"s3cretPassw0rd" and email "alice@example.com"
Then the response status is 500
And no jwt cookie is set