From b590bc09a029becce5cf7aabe7e81d0875e6ad6b Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Sat, 25 Apr 2026 12:34:49 -0400 Subject: [PATCH] fix: require root_url on email_auth_key_url; correct frontend guide for user auth endpoints - Make root_url a required query param on GET /v3/action/user/{id}/email_auth_key_url (previously Optional[str]=None, which produced a malformed link in the emailed URL) - Update GUIDE__AE_API_V3_for_Frontend.md: document root_url as required, add magic link URL format, note valid_email=True side effect, add 404 error, expand 403 conditions for authenticate, add 400 for verify_password when no password is set - Add test_e2e_v3_user_action_routes.py and test_e2e_v3_user_auth_routes.py to tests/README.md Co-Authored-By: Claude Sonnet 4.6 --- app/routers/api_v3_actions_user.py | 2 +- documentation/GUIDE__AE_API_V3_for_Frontend.md | 17 +++++++++++++---- tests/README.md | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/routers/api_v3_actions_user.py b/app/routers/api_v3_actions_user.py index d5263fb..57a4f9c 100644 --- a/app/routers/api_v3_actions_user.py +++ b/app/routers/api_v3_actions_user.py @@ -271,7 +271,7 @@ async def action_new_auth_key( @router.get('/{user_id}/email_auth_key_url', response_model=Resp_Body_Base) async def action_email_auth_key_url( user_id: str = Path(min_length=11, max_length=22), - root_url: Optional[str] = Query(None, min_length=10, max_length=200), + root_url: str = Query(..., min_length=10, max_length=200), key_param_name: str = Query('auth_key', min_length=2, max_length=30), account: AccountContext = Depends(get_account_context), ): diff --git a/documentation/GUIDE__AE_API_V3_for_Frontend.md b/documentation/GUIDE__AE_API_V3_for_Frontend.md index 5b005a3..d63c09e 100644 --- a/documentation/GUIDE__AE_API_V3_for_Frontend.md +++ b/documentation/GUIDE__AE_API_V3_for_Frontend.md @@ -401,7 +401,7 @@ or: **Response on success:** Full user object (same shape as `GET /v3/crud/user/{id}`). -**Errors:** `400` missing credentials, `403` wrong password or account disabled, `404` user not found. +**Errors:** `400` missing credentials, `403` wrong password / account disabled / account not yet enabled / account expired, `404` user not found. > **Auth key flow:** Auth keys are one-time-use — the key is cleared from the DB immediately on successful authentication. Request a new one via `GET /v3/action/user/{id}/new_auth_key`. @@ -421,7 +421,7 @@ Check a user's current password without changing it. ``` or use `"username"` instead of `"user_id"` to look up by username within the account. -**Response:** `data: true` on match. `403` on mismatch, `404` if user not found. +**Response:** `data: true` on match. `400` if the user has no password set, `403` on mismatch, `404` if user not found. --- @@ -474,10 +474,19 @@ Generate a new auth key and email a one-time login link to the user's email addr | Parameter | Type | Default | Description | |---|---|---|---| -| `root_url` | `string` | `null` | Base URL the login link is built from. | +| `root_url` | `string` | *(required)* | Base URL the login link is built from. Must be provided — if omitted the link in the email will be malformed (`None?...`). | | `key_param_name` | `string` | `auth_key` | Query param name used for the auth key in the generated link. | -**Response:** `data: true` on success (email sent). `500` if delivery failed (check account email config and that the user account is enabled with `allow_auth_key = true`). +> [!IMPORTANT] +> `root_url` is **required in practice**. The FastAPI query param accepts `null` but the email builder does not guard against it — omitting it produces a broken link in the email. + +**Magic link URL format (default `key_param_name`):** +``` +{root_url}?user_id={user_id_random}&auth_key={auth_key}&valid_email=True +``` +The frontend at `root_url` should read these query params and call `POST /v3/action/user/authenticate` with `{ "user_id": "...", "auth_key": "..." }`. Note that `valid_email=True` is **always** injected — authenticating via a magic link automatically marks the user's email as verified. + +**Response:** `data: true` on success (email sent). `404` if user not found. `500` if delivery failed — common causes: account email not configured, user `enable = false`, or `allow_auth_key = false`. --- diff --git a/tests/README.md b/tests/README.md index 3bc672b..8c5f337 100644 --- a/tests/README.md +++ b/tests/README.md @@ -19,6 +19,8 @@ These consolidated scripts are the primary verification tool for the V3 API. | `test_e2e_v3_search_engine.py` | **Primary Search**: Basic operators, Registry fields, Nested search, and Filter bypass. | | `test_e2e_v3_security_audit.py` | **Core Security**: Verifies multi-tenant isolation, cross-account write blocking, and ID Vision compliance. | | `test_e2e_v3_auth_security.py` | **Primary Auth**: Site bootstrap, Passcode-to-JWT, and permission boundaries. | +| `test_e2e_v3_user_action_routes.py` | **V3 User Actions**: Sign-in (username+password and auth-key flow), verify password, change password, new auth key, email magic link, and auth guards. | +| `test_e2e_v3_user_auth_routes.py` | **Legacy User Routes**: Tests the pre-V3 `/user/*` endpoints (change_password, new_auth_key, verify_password, lookup, email_auth_key_url, authenticate). | | `test_e2e_v3_actions_file_lifecycle.py` | **Primary Actions**: Upload, Download (ID/Hash/Streaming), and physical Deletion. | | `test_e2e_v3_data_store_lookup.py` | **V3 Parity**: Verifies code-based lookups and latency simulation. | | `test_e2e_redis_extensive.py` | **Redis Stress**: Benchmarks bidirectional ID caching across thousands of records. | @@ -76,6 +78,7 @@ Tests exist to be used — run the relevant suite whenever you touch backend cod | Nested router (`api_crud_v3_nested.py`) changes | `test_e2e_v3_demo_parity.py` | | Search / filter changes | `test_e2e_v3_search_engine.py` | | Auth / account context changes | `test_e2e_v3_security_audit.py`, `test_e2e_v3_auth_security.py` | +| User action route changes (sign-in, password, magic link) | `test_e2e_v3_user_action_routes.py` | | File upload / download changes | `test_e2e_v3_actions_file_lifecycle.py` | | Novi-Mailman bridge changes | `test_e2e_v3_action_novi_mailman.py`, `test_e2e_v3_action_novi_mailman_lists.py` | | Event exhibit tracking export changes | `test_e2e_v3_action_event_exhibit_tracking_export.py` |