-
Notifications
You must be signed in to change notification settings - Fork 2
User
Pair\Models\User is Pair's central authentication model. It extends ActiveRecord and coordinates:
- local login with password verification
- login by external factor (
doLoginById()), useful for Passkey or SSO flows - ACL checks against modules and actions
- session creation and logout
- remember-me cookies and token rotation
- locale, timezone, landing-page, and impersonation helpers
Use User whenever backend code needs authenticated identity, access checks, session bootstrap, password-reset completion, or remember-me behavior.
This is the main entry point for local username or email login.
Current behavior:
- loads the user by
usernameoremaildepending onPAIR_AUTH_BY_EMAIL - rejects disabled users and users with more than 9 failed attempts
- verifies the password with
checkPassword() - creates a
Session, updateslastLogin, resetsfaults, clearspwReset, and writes audit logs - returns an object with
error,message,userId, andsessionId
Typical controller usage:
use App\Models\User;
$timezone = (string)($_POST['timezone'] ?? 'Europe/Rome');
$result = User::doLogin(
trim((string)($_POST['username'] ?? '')),
(string)($_POST['password'] ?? ''),
$timezone
);
if ($result->error) {
$this->toastError('Login failed', (string)$result->message);
return;
}
$user = new User((int)$result->userId);
if (!empty($_POST['remember'])) {
$user->createRememberMe($timezone);
}
$this->redirect('dashboard');Use this when the identity has already been verified by another factor, for example Passkey/WebAuthn, OAuth callback handling, or a trusted SSO flow.
It follows the same safety rules as doLogin(): locked or disabled users are still rejected, and a normal Pair session is created.
Example from an API or Passkey flow:
use App\Models\User;
use Pair\Api\ApiResponse;
$result = User::doLoginById($verifiedUserId, 'Europe/Rome');
if ($result->error) {
ApiResponse::error('UNAUTHORIZED');
}
ApiResponse::respond([
'userId' => $result->userId,
'sid' => $result->sessionId,
]);This closes the session, removes persistent state cookies, unsets remember-me data, resets Application::currentUser, and writes the logout audit entry.
use App\Models\User;
$ok = User::doLogout(session_id());
if ($ok) {
$this->redirect('user/login');
}This is the main ACL check used by Pair.
Current behavior:
- super users always pass
- the
usermodule is always allowed -
publicis always allowed - the method accepts either
module+actionor a singlemodule/actionstring - custom routes are resolved before ACL matching
- rules are loaded once and cached on the user object
Examples:
if (!$user->canAccess('orders', 'edit')) {
throw new \RuntimeException('Access denied');
}if ($user->canAccess('reports/export')) {
// module/action combined in one string
}5) Remember-me lifecycle: createRememberMe(), loginByRememberMe(), renewRememberMe(), unsetRememberMe()
These methods implement the persistent login flow.
createRememberMe():
- generates a random token
- stores only the hashed token in
users_remembers - writes a versioned cookie payload
- keeps only one active remember-me token per user
loginByRememberMe():
- is used automatically by
Applicationduring unauthenticated web requests - validates the cookie, loads the related user, creates a fresh session, rotates the remember-me token, and sets the current application user
renewRememberMe() rotates the cookie and DB token pair.
unsetRememberMe() removes both cookie and server-side token.
Example after a successful login:
$result = \App\Models\User::doLogin($username, $password, $timezone);
if (!$result->error && !empty($_POST['remember'])) {
$user = new \App\Models\User((int)$result->userId);
$user->createRememberMe($timezone);
}Example manual auto-login check in a custom bootstrap path:
if (\App\Models\User::loginByRememberMe()) {
\Pair\Core\Application::getInstance()->redirectToUserDefault();
}landing() returns the default module/action for the user's ACL group. redirectToDefault() turns that into a browser redirect.
$landing = $user->landing();
if ($landing) {
echo $landing->module . '/' . $landing->action;
}$user->redirectToDefault();The main reset path is:
getByPwReset(string $token): ?UsersetNewPassword(string $newPassword, string $timezone): bool
setNewPassword() clears the reset token, stores the new hash, creates a new session, resets faults, and writes the audit event.
use App\Models\User;
$user = User::getByPwReset((string)($_GET['token'] ?? ''));
if (!$user) {
throw new \RuntimeException('Invalid reset token');
}
$user->setNewPassword((string)$_POST['password'], 'Europe/Rome');The low-level helpers are also useful:
-
checkPassword($plain, $hash)verifies a local password -
getHashedPasswordWithSalt($plain)builds the stored hash
impersonate() swaps the active session user while remembering the former user ID. impersonateStop() restores the original user. isSuper() also checks the former user during impersonation so elevated access is preserved correctly.
$admin->impersonate($targetUser);$currentUser->impersonateStop();-
current(): ?staticreturns theApplicationcurrent user ornull. -
avatar(string $classPrefix = 'user'): stringrenders initials with a deterministic template-based color. -
fullName(): stringreturns"name surname". - Virtual properties
fullNameandgroupNameare available through__get(). -
getGroup(): Grouploads the related group. -
getLocale(): Localereturns the stored locale or the default locale. -
getLanguageCode(): ?stringreturns the cached language code derived from the locale relation. -
getDateTimeZone(): DateTimeZonereads the current session timezone or falls back toBASE_TIMEZONE. -
getValidTimeZone(string $timezone): DateTimeZonevalidates an IANA timezone name. -
isSuper(): boolchecks both the current and former impersonating user. -
isDeletable(): boolrefuses self-deletion and then applies the normal ActiveRecord FK checks. -
isLocaleSet(): booltells whetherlocaleIdis currently set.
Authentication hooks:
-
beforeLogin(),afterLogin() afterLoginFailed()-
beforeLogout(),afterLogout()
Remember-me hooks:
-
beforeRememberMeCreate(),afterRememberMeCreate() -
beforeRememberMeLogin(),afterRememberMeLogin() -
beforeRememberMeRenew(),afterRememberMeRenew() -
beforeRememberMeUnset(),afterRememberMeUnset()
These are useful when you need application-specific audit, telemetry, or side effects without rewriting the core flow.
-
PAIR_AUTH_BY_EMAIL=trueswitches local login lookup fromusernametoemail. -
PAIR_SINGLE_SESSION=truedeletes the user's other sessions after a successful login. - The current implementation treats users with more than 9 faults as locked for login purposes.
- Remember-me cookies store a versioned payload, while the DB keeps only a deterministic hash of the token.
See also: Session, Rule, Locale, UserRemember, OAuth2Token, PasskeyAuth.