Accounts and admin
Sign-in is an email address and a password, stored as a Django session cookie. The email is the login and is matched case-insensitively. Public signup does not grant Django staff and does not grant the in-app console. The account can be used before any mail is opened; there is no confirmation link (accounts/views.py).
Password reset is mailed to that address. Development prints mail to the web container log. Production sends it with the SMTP settings in .env.prod.
Two admin surfaces
| Surface | Who | How it is granted |
|---|---|---|
Django /admin/ |
is_staff |
createsuperuser. Public signup never sets this. |
In-app console /app/admin |
is_admin |
python manage.py make_admin you@example.com. This does not grant staff. |
make_admin you@example.com --revoke removes only the console flag, and it refuses when that person is the last active admin. One account can have both flags. Run the command against the stack you mean: compose.dev.yaml or compose.prod.yaml.
The console is online only. The Administration item appears in the side nav only for is_admin. A non-admin who opens /app/admin is sent back to the catalogue.
Console actions
An admin can promote or demote another admin, suspend, ban, delete, restore, edit the name or email, set a storage limit, send a password reset, and sign an account out of every device. Nobody can act on their own account. The last active admin cannot be demoted, suspended, banned, or deleted (console/guards.py).
Suspend is temporary, with an optional end kept in Europe/London. It lifts by itself when that date passes (lift_expired_suspensions in the daily maintenance job), or when an admin lifts it. The catalogue is kept. Ban is permanent, and that email cannot be used to register again. Delete closes the account immediately and erases it after 30 days (ACCOUNT_DELETE_DAYS) unless an admin restores it. Restoring an account whose email is still blocked leaves it banned. Copies can remain in backups for up to 14 further days. Every state except active sets the account inactive, which ends its sessions. A capture already saved on the device stays there.
The console shows counts, storage used, the limit, devices (user agent and last seen), and when the account was last seen. It does not show place names, notes, or photos.
Suspend, lift, ban, unban, delete, restore, and an email change are emailed, including the reason when one was given. If that email fails, the change still stands and the console says so. Every action is written to the audit log, with the addresses copied so the row remains after the account is erased.
Signup can be open, invite only, or closed (SiteSettings). An invite is a single-use link and expires after 7 days (INVITE_DAYS). An announcement can be shown to everyone; dismissing it is remembered on that device.
Routes: /app/admin, /app/admin/users/:id, /app/admin/features, /app/admin/assistant, /app/admin/signup, /app/admin/announcements, /app/admin/health, /app/admin/audit.
Feature switches
console/features.py defines five switches. Each has a global row and an optional per-account row.
| Key | Label | When no per-account row exists |
|---|---|---|
ai_summary |
AI day summary | Follows the global switch, which defaults to on |
sunset_colour |
Sunset colour score | Follows the global switch, which defaults to on |
export |
ZIP export | Follows the global switch, which defaults to on |
trip_assistant |
AI Trip Assistant | Off. The global switch is a kill switch on top of an explicit on |
assistant |
AI Assistant | Off. The global switch is a kill switch on top of an explicit on |
Off hides the feature in the app. The API answers feature_off. An admin cannot change either assistant switch on their own account. Both assistants use the same model and the same daily caps. The assistant tab chooses one model for everybody: Composer 2.5, Grok 4.7, or Claude Sonnet pinned to the catalogue id saved at the time. A newer Sonnet is shown, and it is adopted only when an admin saves again. The tab can set the optional daily caps, and it shows usage and recent errors as counts and codes, never the text of a chat. Usage is split into trip and general. Check models now asks the worker to refresh the catalogue. The health tab shows whether the worker has been seen, whether a Cursor key is configured (yes or no, never the key), how old the catalogue is, and whether the selected model is available. How the worker behaves is in assistants.md.
Storage and appearance
Storage is unlimited until an admin sets storage_limit_bytes. Usage is the sum of stored display JPEGs, including Trash. At or over the limit, a photo upload answers storage_full and the original stays on the device.
New accounts start on the light theme. The last choice is saved on the account, and the newest theme_updated_at wins on other devices.
Where a change lands
| Change | Place |
|---|---|
| User, theme, staff and console flags | accounts/models.py |
| Signup, sign-in, password reset | accounts/views.py, accounts/forms.py |
| Unknown folder and assistant rows on create | accounts/signals.py |
| Console actions and audit | console/actions.py, console/records.py |
| Feature switches | console/features.py |
| Signup mode, invites, announcements | console/models.py |
| Storage total | console/storage.py |
| Console pages | static/tempest/js/ui/admin/ |
| Session identity the client caches | GET /api/me in syncapi/views.py |