Rate Limiting & Brute-Force Protection

A detailed look at how CareNova prevents brute-force attacks, tracks authentication attempts, and protects login endpoints.

Written By Dev010

Last updated 19 days ago

CareNova enforces multiple layers of protection on the login endpoint to prevent brute-force attacks, credential stuffing, and automated login attempts. This guide covers exactly how these protections work, what gets recorded, and how admins can investigate suspicious activity.

How Rate Limiting Works

Rate limiting is enforced by checking the login_attempts table before every login attempt is processed. If the threshold is exceeded, the attempt is blocked before Supabase Auth is ever called β€” no credentials are validated.

The check runs on two dimensions simultaneously:

Dimension

Limit

Window

By email address

5 failed attempts

15 minutes

By IP address

10 failed attempts

15 minutes

Both checks run on every login request. Either one triggering is sufficient to block the attempt.

Example scenarios:

  • A user fails login 5 times in 10 minutes with the same email β†’ blocked by email rule

  • An automated script tries 10 different email addresses from the same IP β†’ blocked by IP rule

  • A user fails 4 times, waits 20 minutes, then fails again β†’ not blocked (window expired)

The Login Attempts Table

Every login attempt β€” successful or failed β€” is recorded in the login_attempts table:

Column

Description

email

Email address used in the attempt

ip_address

IP address of the request

attempted_at

Timestamp of the attempt

success

true for successful login, false for failed

The rate limit query counts rows where success = false and attempted_at is within the last 15 minutes for the given email or IP.

Successful logins are also recorded. This means a successful login after failed attempts does not reset the counter β€” the failed attempts remain in the window until they naturally expire after 15 minutes.

What Happens When a Lockout Triggers

When the rate limit threshold is exceeded:

  1. The login attempt is rejected immediately

  2. Supabase Auth is not called β€” no credential validation occurs

  3. The user receives the message:

   Too many failed login attempts. 
   Please try again in 15 minutes.
  1. The blocked attempt is recorded in auth_audit_log with event type rate_limited

  2. No indication is given of whether the password was correct or not

How the Lockout Lifts

There is no manual lockout reset. The lockout lifts automatically when the oldest failed attempt in the 15-minute window falls outside the window β€” meaning after 15 minutes from the first failed attempt, the count naturally drops below the threshold.

Admins cannot reset a lockout from the dashboard. If a legitimate user is locked out urgently, the quickest resolution is to delete their recent login_attempts records directly in Supabase SQL Editor:

DELETE FROM login_attempts 
WHERE email = 'user@yourdomain.com'
  AND success = false
  AND attempted_at > now() - interval '15 minutes';

The Authentication Audit Log

Every significant authentication event is recorded in the auth_audit_log table:

Event Type

When Recorded

login_success

Successful login

login_failed

Wrong password or unknown email

logout

User signs out

rate_limited

Request blocked by rate limit

account_approved

Admin approves a pending account

account_rejected

Admin rejects a pending account

Each entry captures:

Column

Description

user_id

UUID of the user (null for unknown emails)

email

Email address from the attempt

event

Event type string

ip_address

IP address of the request

user_agent

Browser/client user agent string

metadata

Additional JSON context

created_at

Timestamp

Investigating Suspicious Activity

Admins can query the audit log directly in Supabase β†’ Table Editor β†’ auth_audit_log or via SQL Editor.

View all failed login attempts in the last 24 hours:

SELECT email, ip_address, created_at
FROM auth_audit_log
WHERE event = 'login_failed'
  AND created_at > now() - interval '24 hours'
ORDER BY created_at DESC;

View all activity for a specific email:

SELECT event, ip_address, user_agent, created_at
FROM auth_audit_log
WHERE email = 'user@yourdomain.com'
ORDER BY created_at DESC
LIMIT 50;

Find IPs with the most failed attempts:

SELECT ip_address, COUNT(*) as failed_count
FROM auth_audit_log
WHERE event = 'login_failed'
  AND created_at > now() - interval '24 hours'
GROUP BY ip_address
ORDER BY failed_count DESC
LIMIT 20;

View all rate-limited events:

SELECT email, ip_address, created_at
FROM auth_audit_log
WHERE event = 'rate_limited'
ORDER BY created_at DESC;

Session Tracking

Beyond login attempts, CareNova tracks active sessions in the user_sessions table. Each session record includes:

Column

Description

user_id

The authenticated user

ip_address

IP address at login

user_agent

Browser and device info

device_info

Parsed device string

last_active_at

Last activity timestamp

expires_at

Session expiry time

is_revoked

Whether session was manually revoked

Session activity is updated at most every 5 minutes per session to avoid excessive database writes.

Revoking a Session

If an admin suspects a compromised session, they can revoke it directly in SQL Editor:

UPDATE user_sessions 
SET is_revoked = true 
WHERE user_id = 'paste-user-uuid-here';

The user will be redirected to the login page on their next request.

Automatic Cleanup

The login_attempts and auth_audit_log tables grow over time as authentication events are recorded. The cron job at /api/cron/cleanup-auth handles cleanup:

Table

Cleanup Rule

login_attempts

Deletes records older than 24 hours

auth_audit_log

Deletes records older than 24 hours

user_sessions

Deletes expired and revoked sessions

This keeps the tables lean and ensures the rate limiting queries remain fast β€” they only scan recent records.

Without the cron job running, these tables will grow indefinitely and rate limit queries will slow down over time. See the Cron Setup guide to configure the cleanup schedule.

Password Policy Enforcement

Rate limiting is complemented by a strict password policy enforced at the application level. All passwords must meet:

Rule

Requirement

Minimum length

8 characters

Uppercase letter

At least one

Lowercase letter

At least one

Number

At least one

Special character

At least one (@$!%*?&)

Additionally, the following passwords are explicitly blocked:

password, password123, 123456789, 
admin123, clinic123, carenova123, welcome1

These blocked passwords are rejected regardless of meeting the structural requirements above.

Security Best Practices for Admins

  • Enforce strong passwords β€” remind staff of the password policy when onboarding

  • Review audit logs regularly β€” check auth_audit_log weekly for unusual patterns, especially rate_limited events

  • Monitor IP addresses β€” a high volume of failed attempts from one IP indicates an automated attack

  • Do not share credentials β€” each staff member should have their own account so audit logs are meaningful

  • Run the cron job daily β€” without cleanup, rate limiting degrades over time

  • Revoke sessions immediately if a staff member's account is suspected compromised

Troubleshooting

Legitimate user locked out:

  • Delete their recent failed attempts using the SQL query above

  • The lockout will lift immediately

Rate limiting not working after many failed attempts:

  • Confirm the cron job is not running too frequently β€” if cleanup runs every few minutes it could be deleting attempts before the window closes

  • Confirm the cron schedule is daily, not every few minutes

  • Check that login_attempts table exists and is being written to

Audit log growing very large:

  • Confirm the cron job is running daily at /api/cron/cleanup-auth

  • Check Vercel β†’ Functions logs or your cron service for job execution history

  • Run the cleanup manually once to reduce the backlog:

curl -X GET \
  -H "Authorization: Bearer YOUR_CRON_SECRET" \
  https://yourdomain.com/api/cron/cleanup-auth

Next Step

Continue to Roles & Permissions to learn how CareNova controls what authenticated users can see and do inside the platform.