@ooneex/user package defines the identity contract for the framework. It ships only types and enums — no runtime, no storage, no logic. IUser describes who a request belongs to: email, roles, profile fields, two-factor state, and references to sessions, accounts, and verifications. Auth resolves a user from a token and attaches it to the request context; permission checks and controllers read it from there. Because it is a pure contract, every layer agrees on the same shape without depending on a database or auth provider.
Why this package
- One identity shape. Every part of the stack — auth, roles, permissions, controllers — speaks the same
IUserinterface, so a user resolved in one place is usable everywhere. - Provider-agnostic. The model is independent of how you authenticate. A Clerk, JWT, or credentials backend maps its own user into
IUser; downstream code never sees the provider. - Zero runtime. Pure
type/interface/enumdefinitions. Importing it adds nothing to your bundle and forces no storage decisions. - Audit-aware. Sessions, accounts, verifications, and profile updates each have a dedicated interface with timestamps and status, so security trails are modeled, not improvised.
- Roles are first-class.
rolesis a required field of uppercase strings, the same convention roles and route guards expect.
How it works
A user does not arrive with the request — it is resolved. Auth middleware reads the bearer token, validates it, maps the result into anIUser, and sets context.user. From that point the same object travels through the pipeline.
| Stage | What happens to the user | |
|---|---|---|
| Request arrives | context.user is null — no identity has been established yet. | |
| Auth middleware | Validates the token, builds an IUser, and assigns context.user. A guest route may leave it null. | |
| Permission checks | setUserPermissions(context) reads context.user and its roles to grant abilities. | |
| Controller | Reads context.user (typed `IUser | null`) to scope data to the caller. |
@ooneex/controller, the context types the user as nullable so guest-accessible routes are handled explicitly:
The IUser interface
IUser extends a shared IBase (lifecycle and moderation fields) and adds identity, profile, verification, and security fields. email and roles are required; everything else is optional.
| Field | Type | Meaning |
|---|---|---|
email | string | Primary email address. Required. |
roles | Uppercase<string>[] | Roles granted to the user (e.g. ["ROLE_USER"]). Required; consumed by roles and route guards. |
externalId | string | Identifier from an external auth provider (e.g. a Clerk user id). |
name | string | Full display name. |
firstName | string | Given name. |
lastName | string | Family name. |
username | string | Unique handle. |
avatar | string | URL of the profile picture. |
bio | string | Short biography / about text. |
phone | string | Phone number. |
birthDate | Date | Date of birth. |
timezone | string | Preferred timezone. |
isEmailVerified | boolean | Whether the email has been verified. |
isPhoneVerified | boolean | Whether the phone has been verified. |
lastActiveAt | Date | Last activity timestamp. |
emailVerifiedAt | Date | When the email was verified. |
phoneVerifiedAt | Date | When the phone was verified. |
lastLoginAt | Date | Last successful login. |
passwordChangedAt | Date | When the password was last changed. |
twoFactorEnabled | boolean | Whether 2FA is enabled. |
twoFactorSecret | string | Secret backing the 2FA flow. Store encrypted, never expose. |
recoveryTokens | string[] | One-time recovery codes. |
sessions | ISession[] | Active and historical sessions for the user. |
accounts | IAccount[] | Linked authentication accounts (OAuth, credentials, WebAuthn). |
verifications | IVerification[] | Pending or completed verification records. |
Inherited IBase fields
| Field | Type | Meaning |
|---|---|---|
id | string | Unique identifier. Required. |
isLocked / lockedAt | boolean / Date | Whether the account is locked, and when. |
isBanned / bannedAt / banReason | boolean / Date / string | Ban flag, timestamp, and reason. |
isBlocked / blockedAt / blockReason | boolean / Date / string | Block flag, timestamp, and reason. |
isPublic | boolean | Whether the record is publicly visible. |
createdAt / updatedAt / deletedAt | Date | Lifecycle timestamps (soft-delete via deletedAt). |
language | LocaleType | Preferred locale. |
Related identity interfaces
IUser references three companion interfaces, each also extending IBase.
ISession— an authentication session:token, optionalrefreshToken, device and location metadata (userAgent,ipAddress,deviceType,browser,operatingSystem,location),isActive,expiresAt, and revocation fields (revokedAt,revokedReason). See JWT for how tokens are minted and validated.IAccount— a linked credential or provider:type(anEAccountType), a hashedpasswordfor credentials accounts, OAuth fields (provider,providerAccountId,accessToken,refreshToken,scope,idToken), and provider profile data.IVerification— a verification challenge:token,type(anEVerificationType), optionalcode,isUsed,expiresAt, andattemptsCount/maxAttemptsfor rate limiting.
IUserProfileUpdate interface audits profile edits, recording changedFields, previousValues / newValues, a status (EProfileUpdateStatus), and an optional linked verification.
Enums
The package exports three string enums, each with a matching string-literal union type (AccountType, VerificationType, ProfileUpdateStatusType) for use where a plain string is preferred.
| Enum | Members | Used by |
|---|---|---|
EAccountType | OAUTH "oauth", EMAIL "email", CREDENTIALS "credentials", WEBAUTHN "webauthn" | IAccount.type |
EVerificationType | EMAIL "email", PHONE "phone", PASSWORD_RESET "password_reset", TWO_FACTOR "two_factor", ACCOUNT_ACTIVATION "account_activation" | IVerification.type |
EProfileUpdateStatus | PENDING "pending", COMPLETED "completed", FAILED "failed", REVERTED "reverted" | IUserProfileUpdate.status |
Resolving a user in auth middleware
An auth middleware validates the incoming token, maps the provider’s user into anIUser, and assigns it to context.user. Optional fields are only set when present, so the resolved user stays minimal:
Using the resolved user
Downstream code readscontext.user rather than re-validating the token. Because it is IUser | null, always handle the guest case.
In a controller, scope data to the caller:
setUserPermissions reads the user and its roles to grant abilities, which can / cannot then check:
IPermission contract and Roles for how roles strings are defined and compared.
Best practices
- Never store plaintext passwords.
IAccount.passwordis the hashed credential; hash on write and compare hashes on login. TreattwoFactorSecretandrecoveryTokensas secrets too — encrypt at rest, never serialize to clients. - Resolve once, reuse everywhere. Validate the token and build
IUserin auth middleware, setcontext.user, and read it downstream. Don’t re-decode tokens in controllers or permissions. - Keep the identity model minimal. Populate only the fields you have. Optional fields exist for richer profiles, not as a checklist to fill.
- Always handle
null. The context types the user asIUser | null; branch on it explicitly so guest routes are intentional, not accidental. - Expose a safe view. When returning a user over the wire, project to the public fields — never send credentials, secrets, sessions, or verification tokens.
- Trust
roles, not ad-hoc flags. Authorize against therolesarray via roles and permissions rather than inventing per-controller checks.