Blog
EngineeringMarch 24, 20264 min read

How We Made Tripplet More Stable in March

Password reset tokens, rate limiting, and error handling — what we tightened up and why.

T
Tripplet Team
tripplet.ai

Stability isn't glamorous. Nobody tweets about "we fixed our rate limiting." But it's the kind of work that makes the difference between a platform you can trust and one that makes you nervous. This week we shipped a focused round of auth and stability improvements. Here's the full breakdown.

The password reset token fix

We had a subtle but real security issue in our password reset flow. When a user requested a password reset, we generated a random token, hashed it with SHA-256, and stored the hash in the database. So far, so good. The bug: we were also putting the hash in the reset URL instead of the raw token.

This completely defeats the purpose of hashing. If the URL contains the hash, and the database contains the hash, then anyone who intercepts the URL (from logs, email headers, browser history) already has exactly what they need to query the database directly. The raw token was generated but thrown away unused.

The fix is the classic pattern: raw token in the URL, hash in the DB. When a reset link is clicked, we hash the incoming URL token and compare it to what's stored. An attacker who sees the URL can't compute the hash preimage. An attacker who gets the DB can't compute the URL token. Both sides are now protected independently.

We also clean up any existing reset tokens for a user before issuing a new one — no more token pile-up in the database from forgotten requests.

Rate limiting on auth endpoints

None of our auth endpoints had rate limiting. Login, registration, and forgot-password were all wide open to automated abuse.

We added strict limits: 10 login attempts per 15 minutes per IP (after which you get a 429 with a Retry-After header), 5 registration attempts per hour per IP, and 3 password reset requests per hour per IP. These limits are tight enough to stop any realistic brute-force or spamming attempt, but generous enough that a real user who mistyped their password a few times won't notice.

All auth rate limits are keyed by IP, not by user, so they apply equally to both existing accounts and accounts that don't exist yet. This matters for the forgot-password endpoint specifically — we return the same success response whether or not an account exists (to prevent email enumeration), so the rate limit is the only real protection there.

AuthContext retry on transient failures

On page load, Tripplet fetches the current user from /api/auth/me to restore session state. Previously, if this request failed due to a transient server error — a cold start, a brief DB hiccup — the auth context would immediately conclude "not logged in" and show the signed-out UI.

We added a single retry with a short delay. If the first request returns a 5xx error or throws a network exception, we wait 800ms and try once more. For genuine session absences (no cookie, expired token) the first request returns a clean response, so there's no extra latency. The retry only fires when something actually went wrong server-side.

Error message hygiene

The registration endpoint was returning raw Supabase error messages on failure. These messages can include table names, constraint names, and other internal details that shouldn't be visible to users. We replaced that with a generic "Failed to create account. Please try again." that gives users something actionable without leaking implementation details.

We also added server-side email format validation to registration. Previously, an invalid email address would pass client-side validation, reach the database, and fail on a DB constraint — returning whatever error message Supabase gave us. Now we validate the format before touching the database.

What's next

There are more stability improvements in the pipeline. We're looking at adding structured error codes to all auth responses (so the client can distinguish "wrong password" from "account doesn't exist" from "service unavailable" without parsing error strings), implementing a proper refresh token strategy so sessions extend gracefully rather than expiring hard after 7 days, and adding a token revocation list for logout.

None of this is visible to users when it's working correctly. That's the point.

Was this helpful?

47Insightful
31Well written
62Loved it

Enjoyed this post?

Try Tripplet for free

Unlimited messages, no credit card required.

More from the blog