conceptadev / conceptadev/rockets

test: pin the weak-password signup path (rejected, no orphan account, retry succeeds)

Open
#112 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1
Forks
2
Avg merge
2d 10h
Merged PRs (30d)
23

Description

### Use case

Signup rejects a weak password with `400 PASSWORD_NOT_STRONG_ERROR` and
leaves no user row behind — but **nothing in the suite proves it**, and
the reason is worth stating plainly: the password strength policy
defaults to `PasswordStrengthEnum.None` (0) outside production and
`VeryStrong` (4) inside it.

```js
// @concepta/nestjs-password — password-default.config.js
minPasswordStrength: process.env.PASSWORD_MIN_PASSWORD_STRENGTH
? Number.parseInt(...)
: process.env.NODE_ENV === 'production'
? PasswordStrengthEnum.VeryStrong
: PasswordStrengthEnum.None,
```

So no test password ever fails the strength check, and the whole branch
— reject, roll back, retry — is exercised for the first time in
production.

That branch is also load-bearing in a way that is easy to miss.
Upstream's `CreateUserHandler` persists the user **before** validating
the password (reported as conceptadev/nestjs-modules#469), so the
absence of an orphan account depends entirely on the surrounding
`TransactionScope.run` rolling back. `run()` fails **open** when no
transaction factory is registered for the store, so the same code on an
adapter without one leaves an account that cannot sign up again (address
taken) and cannot log in (no credentials).

Verified by hand on the current branch, with
`PASSWORD_MIN_PASSWORD_STRENGTH=4` and the SQLite/TypeORM e2e app:

```
POST /signup { password: 'password123' } -> 400 PASSWORD_NOT_STRONG_ERROR
select * from user where username='orphan' -> []
POST /signup { password: } -> 201
POST /token/password -> 200
```

Correct today. Untested, so a regression — ours or upstream's — would
ship silently to the only environment where the policy is on.

### Proposal

Add an e2e in `packages/rockets-server-auth` that sets the policy
explicitly and pins the whole sequence:

1. `POST /signup` with a password that passes the request schema
(`.min(8)`) but fails the strength policy → `400`
`PASSWORD_NOT_STRONG_ERROR`.
2. **No user row persisted** — asserted against the repository, not
inferred from the status code. This is the assertion that actually
fails if the rollback stops happening.
3. `POST /signup` again with a strong password and the same address →
`201`, not `400 USER_DUPLICATE_ERROR`.
4. `POST /token/password` with the new credentials → `200`.

Step 3 is the one that matters to a consumer: it is the difference
between "the user retries and it works" and "the address is permanently
unusable".

The policy has to be forced for the test, since the default is `None`
outside production — `PASSWORD_MIN_PASSWORD_STRENGTH=4` before the app
boots is enough (the setting is read through `registerAs` at module
init).

While in there, consider covering the same shape on the other route that
takes a password from an unauthenticated caller — invitation acceptance
— which goes through the same upstream ordering.

### Out of scope

- Reordering signup to validate before writing. The ordering belongs to
upstream (conceptadev/nestjs-modules#469); doing it in this repo costs
either a globally registered `PasswordModule` or a second bcrypt round
on a public route, and buys nothing on the shipped adapters.
- Anything on #105 — this is independent of that PR.

Contributor guide

Open the contributing guide

Research direction

Work in packages/rockets-server-auth and use the SQLite/TypeORM e2e app; set PASSWORD_MIN_PASSWORD_STRENGTH=4 before the app boots. Exercise POST /signup with a weak password, inspect the repository for no persisted user row, then retry with a strong password and call POST /token/password. Done means the sequence returns 400 with PASSWORD_NOT_STRONG_ERROR, leaves no row, then returns 201 and 200.

Written by the indexing model from the issue text.

Assessment

Tech stack
sqlite, typescript
Domain
authentication, backend, testing
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.