feat(registraton): add anti enumeration (cannot know if an account already exists)

Important: anti-enumeration is active only if SMTP is defined, welcome email can be used
    otherwise it is a classic registration with ok or conflic if account alrady exists
This commit is contained in:
Edouard Vanbelle
2026-06-02 22:50:13 +02:00
parent 46f5379def
commit 00af0e8a89
4 changed files with 248 additions and 73 deletions
+82 -11
View File
@@ -25,8 +25,11 @@ alice_token: jsonpath "$.access_token"
# ─────────────────────────────────────────────────────────────
# Step 2 — Classic registration (with password) still works.
# Returns 201 + UserDto (existing behaviour, unchanged).
# Step 2 — Classic registration (with password). PR 20 anti-
# enumeration mode (SMTP wired) returns a uniform 200
# regardless of success or collision. No UserDto in
# the response — the frontend logs the user in
# separately to get a session.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/register
Content-Type: application/json
@@ -36,13 +39,23 @@ Content-Type: application/json
"password": "TestPassword1!"
}
HTTP 201
HTTP 200
[Asserts]
jsonpath "$.username" == "charlie"
jsonpath "$.email" == "charlie@example.com"
jsonpath "$.is_external" == false
jsonpath "$.message" contains "request received"
# ─────────────────────────────────────────────────────────────
# Step 2b — Log in as charlie to confirm registration succeeded
# AND to capture her user_id for cleanup.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/login
Content-Type: application/json
{ "username": "charlie", "password": "TestPassword1!" }
HTTP 200
[Captures]
charlie_user_id: jsonpath "$.id"
charlie_token: jsonpath "$.access_token"
charlie_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -58,7 +71,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.message" contains "sign-in link"
jsonpath "$.message" contains "request received"
# ─────────────────────────────────────────────────────────────
@@ -109,7 +122,7 @@ pr18_user_id: jsonpath "$.id"
# ─────────────────────────────────────────────────────────────
# Step 7 — Dave can request another magic-link (he has no
# Step 7 — The new user can request another magic-link (no
# password configured → eligible). Anti-enumeration
# 200 either way.
# ─────────────────────────────────────────────────────────────
@@ -123,8 +136,66 @@ jsonpath "$.message" contains "sign-in link"
# ─────────────────────────────────────────────────────────────
# Cleanup — admin deletes charlie + dave so the DB-clean sweep
# at run.sh end sees no stragglers.
# Step 8 — PR 20 anti-enumeration: register with charlie's
# email AGAIN (different password). Response is the
# same uniform 200 — attacker can't tell from the
# HTTP shape whether the email was already taken.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/register
Content-Type: application/json
{
"username": "charlie-imposter",
"email": "charlie@example.com",
"password": "AttackerPassword99!"
}
HTTP 200
[Asserts]
jsonpath "$.message" contains "request received"
# ─────────────────────────────────────────────────────────────
# Step 9 — Verify the collision was silently suppressed: the
# attacker's password does NOT work (the original
# row is intact, no rewrite happened).
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/login
Content-Type: application/json
{ "username": "charlie@example.com", "password": "AttackerPassword99!" }
HTTP 403
# ─────────────────────────────────────────────────────────────
# Step 10 — Charlie's original password still works — the
# collision didn't touch her account.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/login
Content-Type: application/json
{ "username": "charlie", "password": "TestPassword1!" }
HTTP 200
# ─────────────────────────────────────────────────────────────
# Step 11 — Username collision (different email): same uniform
# 200, no new user, audit `username_taken`.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/register
Content-Type: application/json
{
"username": "charlie",
"email": "charlie-other@example.com",
"password": "AttackerPassword99!"
}
HTTP 200
[Asserts]
jsonpath "$.message" contains "request received"
# ─────────────────────────────────────────────────────────────
# Cleanup — admin deletes both test users.
# ─────────────────────────────────────────────────────────────
DELETE {{base_url}}/api/admin/users/{{charlie_user_id}}
Authorization: Bearer {{alice_token}}