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?
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.