JOB-EASY

Authentication

Custom credentials auth, explicit sessions, and robust identity lifecycle management.

Job-Easy does not use third-party libraries like NextAuth or Auth.js. Instead, it relies on a bespoke, highly secure credentials-based authentication flow with explicitly modeled sessions.

The Session Model & Hashing

When a user logs in, a Session record is created. The browser cookie receives a random secret token, but the database only ever stores a SHA-256 hash of that token (tokenHash).

Why hash session IDs?

Because the Active Sessions screen lists a user's other devices. If we stored the raw ID, an admin reading the database or a user viewing their devices could potentially expose live bearer tokens. Hashing the token makes it completely inert—safe to return, log, or embed in an RSC payload.

Route Guards & Status Enforcement

The authoritative check for a protected route is the requireActiveUser guard, which explicitly enforces the user's status. The session loader itself intentionally ignores status so that the application layout can still safely render an <AccountBlocked> screen for suspended users.

The Status Vocabulary

  • active: Normal operation.
  • pending: Awaiting admin approval after registration.
  • suspended / banned: Blocked by an administrator.
  • deactivated: A reversible pause initiated by the user.
  • deleting: An irreversible lock indicating the account is being asynchronously purged.

Email Verification (OTP)

Job-Easy uses 6-digit OTP codes rather than magic links to support users opening the mail on a different device than the one they are signing up on. To prevent database collision attacks on the 6-digit space (since sha256("123456") is constant), the digest is explicitly salted with the user's ID (hashScopedToken) before being saved.

Account Deletion (Two-Phase Purge)

Deleting a user is a two-phase process designed to safely revoke all third-party grants without locking the user in a broken state.

  • Phase 1 (Atomic): The account status flips to deleting. Active sessions are immediately destroyed and the user is logged out. A snapshot of their email and Google Refresh Token is taken.
  • Phase 2 (Async): A background job takes over to fire the farewell email, hit the Google API to revoke OAuth grants, purge private Cloudinary assets, and finally delete the database row.