Framework
Subsystems

Credentials

A credential is an authenticated identity — its email and password, an access level, and a profile. The Credential class is the schema-backed model you look up, create and edit; signing one in lives in Authentication.

Finding credentials

Credentials are stored as a model, so they come with typed lookups and lists:

MethodReturns
getByID(id)One credential by id.
getByEmail(email)One credential by email.
getByAccessToken(token)The credential behind an access token.
exists(id) / emailExists(email)Existence checks.
getList($query) / search($text)A paginated list, and a type-ahead over the names and email.
getSelect($query) / getTotal($query)An option list for a dropdown, and the count behind a listing.
use Framework\Auth\Credential;
use Framework\Auth\Schema\CredentialQuery;

// One credential
$credential = Credential::getByID($credentialID);
$credential = Credential::getByEmail($email);

if ($credential->isEmpty()) {
    return Response::error("CREDENTIAL_NOT_FOUND");
}

The lookups take a complete flag that decides how much of the row is read — the list ones take a query, so they filter and page like any other model:

$query = new CredentialQuery();
$query->access->equal(Access::Admin);
$query->status->equal(CredentialStatus::Active);
$query->createdTime->orderByDesc();

$total = Credential::getTotal($query);
$query->paginate($page, $amount);
$list  = Credential::getList($query);

// Or a type-ahead over the names and the email
$results = Credential::search($text, amount: 10);

For the interface there is getName(), which assembles the display name from whatever it is given — an entity, a row, or a joined result with a prefix — falling back to the email when there is no name:

Credential::getName($credential);                    // "Ada Lovelace"
Credential::getName($credential, withEmail: true);   // "Ada Lovelace <ada@example.com>"
Credential::getName($row, prefix: "owner");          // from ownerFirstName / ownerLastName

Creating & editing

Create, edit and remove credentials through the same class. create() hashes the password and stores the row, edit() updates it, delete() soft-deletes while destroy() removes it for good:

use Framework\Auth\Credential;

$credentialID = Credential::create($request);
Credential::edit($credentialID, $request);
Credential::delete($credentialID);

Passwords

Passwords are kept as a salted hash, never in plain text:

MethodDoes
setPassword(id, password)Hash and store a new password.
isPasswordCorrect(id, password)Verify a password against the stored hash.
setTempPass(id, hours)Issue a temporary password, good for 48 hours by default.
reqPassChange(id) / setReqPassChange(id, bool)Whether a password change is pending, and forcing one.
// Signing in — verify against the stored hash
if (!Credential::isPasswordCorrect($credentialID, $password)) {
    return Response::error("PASSWORD_INCORRECT");
}

// Changing it — setPassword hashes, salts and clears any pending change
Credential::setPassword($credentialID, $newPassword);

// Or force one on the next sign in
Credential::setReqPassChange($credentialID, true);
if (Credential::reqPassChange($credentialID)) {
    // ... send the user to the change screen
}

// A recovery link issues a temporary password, valid for 48 hours by default
$tempPass = Credential::setTempPass($credentialID, hours: 24);

Profile & preferences

Beyond the login, the model carries the user's profile and preferences, each with its own setter:

SetterSets
setEmail / setAccessThe email and access level.
setAvatarThe profile image.
setLanguage / setTimezone / setAppearanceLocale and UI preferences.
setStatusActivate, block or otherwise flag the account.
updateLoginTimeRecord the last sign-in.
Credential::setEmail($credentialID, $email);
Credential::setAccess($credentialID, Access::Admin);
Credential::setStatus($credentialID, CredentialStatus::Inactive);
Credential::setLanguage($credentialID, "es");
Credential::setTimezone($credentialID, -180);
Credential::updateLoginTime($credentialID);

Each one writes a single column, so a screen that changes one preference does not have to send the whole profile back. For anything without its own setter there is the generic pair, which takes a column rather than a string:

Credential::getValue($credentialID, CredentialColumn::Phone);
Credential::setValue($credentialID, CredentialColumn::Phone, $phone);

Devices

Every device a credential signs in from is tracked by Device, which stores the OneSignal player id used to reach it — that is how push notifications know where to deliver. Signing in registers the device; signing out removes it:

MethodDoes
Device::add(…)Register a device for a credential.
Device::getAllForCredential(id)The credential's device player ids.
Device::has(…) / Device::remove(…)Check for, or remove, a device.
Signing a credential in, the JWT tokens and the current-user session all live in Authentication.