fix(bunyip-api): handler correctness for webhooks, admin, auth, dedup #132
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/bunyip-77"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Stripe webhook: a checkout.session.completed without our user_id metadata now returns Ok(()) and skips instead of 4xx-ing, ending the infinite Stripe retry storm; the lifetime price lock now reads the price id from the session line items rather than storing the subscription id (sub_...) in locked_price_id.
Admin endpoints: PUT users/{id}/status with active=true now reactivates (new UserRepository::restore clears deleted_at) and 404s only when no soft-deleted row exists, instead of always 422-ing; POST /admin/test-email switches from AuthenticatedUser to AdminUser so a non-admin can no longer trigger their own welcome email; get_system_health now reuses a shared collect_dashboard_stats helper (no duplicated count SQL) and propagates DB errors instead of swallowing them via .ok().flatten(); SystemHealth.uptime_seconds is populated from a process-start Instant registered in app state.
Auth redirect: auth_redirect now validates the target against config.web_origin (like logout_redirect) so redirects stop 422-ing when COOKIE_DOMAIN is unset, and its per-request cookie/target diagnostics drop from info! to debug!.
Dedup: the byte-for-byte check_rate_limit copies in auth.rs and totp.rs and the inline variant in feedback.rs collapse to one shared handlers::check_rate_limit. The JwtService extraction unifies on the documented canonical path req.app_data::<Arc>() (jwt is registered as a bare Arc, so the web::Data<Arc> form in admin.rs would 500); admin_reset_password and impersonate_user move to that path.
Misc: the dev-only no-payment POST /v1/memberships/subscribe route and handler are removed; the unused PaginationQuery.page field and the leftover ListFeedbackQuery.page_size alias are dropped; export_feedback now surfaces request_id on the response instead of discarding it; delete_account calls revoke_op_sessions so OIDC OP/SSO sessions die with the account (as logout_all does); oci_auth::too_many receives the RateLimitConfig that actually tripped so Retry-After is correct for throughput and per-IP denials instead of 0.
The application_field arbitrary-field finding does not apply: application updates already flow through the typed UpdateApplication struct, so only defined columns are settable.
#BUNYIP-77
Branch-backed replacement for #122 (original was AGit/branchless, no merge button once behind main). Rebased clean onto current main.
Stripe webhook: a checkout.session.completed without our user_id metadata now returns Ok(()) and skips instead of 4xx-ing, ending the infinite Stripe retry storm; the lifetime price lock now reads the price id from the session line items rather than storing the subscription id (sub_...) in locked_price_id. Admin endpoints: PUT users/{id}/status with active=true now reactivates (new UserRepository::restore clears deleted_at) and 404s only when no soft-deleted row exists, instead of always 422-ing; POST /admin/test-email switches from AuthenticatedUser to AdminUser so a non-admin can no longer trigger their own welcome email; get_system_health now reuses a shared collect_dashboard_stats helper (no duplicated count SQL) and propagates DB errors instead of swallowing them via .ok().flatten(); SystemHealth.uptime_seconds is populated from a process-start Instant registered in app state. Auth redirect: auth_redirect now validates the target against config.web_origin (like logout_redirect) so redirects stop 422-ing when COOKIE_DOMAIN is unset, and its per-request cookie/target diagnostics drop from info! to debug!. Dedup: the byte-for-byte check_rate_limit copies in auth.rs and totp.rs and the inline variant in feedback.rs collapse to one shared handlers::check_rate_limit. The JwtService extraction unifies on the documented canonical path req.app_data::<Arc<JwtService>>() (jwt is registered as a bare Arc, so the web::Data<Arc<JwtService>> form in admin.rs would 500); admin_reset_password and impersonate_user move to that path. Misc: the dev-only no-payment POST /v1/memberships/subscribe route and handler are removed; the unused PaginationQuery.page field and the leftover ListFeedbackQuery.page_size alias are dropped; export_feedback now surfaces request_id on the response instead of discarding it; delete_account calls revoke_op_sessions so OIDC OP/SSO sessions die with the account (as logout_all does); oci_auth::too_many receives the RateLimitConfig that actually tripped so Retry-After is correct for throughput and per-IP denials instead of 0. The application_field arbitrary-field finding does not apply: application updates already flow through the typed UpdateApplication struct, so only defined columns are settable. #BUNYIP-77