Users and Roles
Accounts, roles, and the dot-notation permissions that guard routes and templates.
On this page
Users
A user is a folder at site/data/users/{username}/ with an _index.json and a _data.json, the same split as a page. The role and hashed password live in _data.json.
phlat user:create adam --role=admin
phlat user:role adam editor
phlat user:password adam
A built-in guest user stands in for anyone not signed in. It holds the guest role, which grants nothing by default.
Roles
A role is a folder with an _index.json listing permissions. Site roles are checked before the framework's:
phlat/roles/admin/_index.json ← ships with the framework
phlat/roles/guest/_index.json
site/roles/editor/_index.json ← yours
{
"locked": false,
"permissions": {
"admin": { "access": true },
"pages": { "edit": true, "publish": true }
}
}
Permissions are nested and checked with dot notation: pages.publish looks up permissions.pages.publish. A * key at any level is a wildcard for everything not listed there; the admin role is { "*": true }. A missing permission is denied.
A signed-out visitor is checked against the guest role, so can() and requirePermission() work the same for everyone. To grant guests a permission, add site/roles/guest/_index.json. A site role replaces the framework's role of the same name.
Checking permissions
requirePermission() guards a route. It redirects to login when nobody is signed in and throws ForbiddenException (403) when the user lacks the permission:
$page->router->post('publish', function () use ($app, $page) {
$app->session->requirePermission('pages.publish');
// ...
});
requireLogin() accepts any signed-in user. can() checks without stopping the request:
if ($app->session->can('pages.edit')) { /* ... */ }
In Latte:
{if permission('pages.edit')}
<a href="{$page->url()}/edit">Edit</a>
{/if}
Put checks in a before() hook to guard a whole section (see Routing). To restrict a page and everything under it to signed-in users or specific roles, set its access instead. See Publishing and access.
Profile fields
Extra user fields are declared in roles/_index.json under fields, like a view declares page fields. Each name refers to a field in site/fields/, or extends a core one ("avatar": { "extends": "@phlat/avatar" }), and values read like page fields.
{
"fields": {
"password": { "required": true },
"avatar": {},
"jobTitle": {}
}
}
Rate limiting
$app->session->throttle($key, $max, $window = 300) records one attempt for a key and returns true once more than $max attempts have been recorded within the window. Attempts are counted in SQLite, so concurrent requests each get their own number. Login uses it to slow repeated failures; use it for any form.