LibrePortal/containers/stoat/tools/stoat.tools.json
librelad 93ec260298 Provision a Stoat owner account, add create/reset user tools
Stoat shipped with no account and no way to make one from LibrePortal. It is
first-come-first-served, with invite_only=false, no captcha and no email
verification, so every install left a window between the API answering and
someone signing up in which anyone who could reach the port could take the
instance. The installer now claims the configured account as soon as the API
responds, and prints the credentials instead of "go and register".

Provisioning goes over HTTP, not Mongo: an account needs a login AND a
completed onboarding (accounts holds one, users the other) and passwords go
through Stoat's argon2 layer. Failure is deliberately non-fatal — it leaves the
instance exactly as it was before this existed, which must not fail an
otherwise good install of sixteen containers.

Both obvious config defaults are rejected by Stoat, which is only visible as a
failed install, so both are chosen against its rules: example.com comes back
DisallowedContactSupport (reserved domain) hence admin@stoat.local, and "admin"
comes back InvalidUsername (reserved) hence "administrator".

Two of the three missing adapter operations are now implemented:

- createUser: create, log in, complete onboarding. Without the last step an
  account can sign in and then sits on a pick-a-username screen forever.

- setPassword: previously excluded because hand-rolling argon2 risks writing a
  hash nothing can verify, locking the holder out with no error at the time.
  That objection is answered by refusing to hash at all — authifier already
  owns a reset flow, so this writes only its password_reset token to Mongo and
  lets PATCH /auth/account/reset_password do the hashing with the same code
  that verifies. Verified: reset by username and by email, new password logs
  in, token consumed.

setAdmin is still NOT implemented, and the header now says so with evidence
rather than assertion. Stoat has no instance-level admin flag: the user
document holds only _id/username/discriminator and GET /users/@me adds only
relationship and online. Permissions are per-server bitfields on server_members.
A "make admin" button would invent a concept the app does not have.

Also fixed two things found while testing:

- post_start returned early when the public URL needed no settling, which
  skipped everything after it — so provisioning would have been silently
  missed on exactly the domain-backed installs that guessed the URL right.

- _stoatBaseUrl advertised $public_ip_v4, the WAN address from an external
  resolver, in URLs compiled into the web client. Same fix as the APP_URL
  processor: prefer $local_ip_v4, since LibrePortal never forwards ports.

Verified end to end on a clean install: the owner account is created and
onboarded, the generated password logs in, both new tools run through
`libreportal app tool`, and a created account survives a password reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 00:37:32 +01:00

94 lines
2.4 KiB
JSON

{
"tools": [
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Register an account and finish its onboarding, so it can sign in straight away.",
"icon": "👤",
"fields": [
{
"name": "email",
"label": "Email",
"type": "text",
"placeholder": "user@stoat.local",
"required": true
},
{
"name": "password",
"label": "Password",
"type": "password",
"placeholder": "Leave blank to generate"
},
{
"name": "username",
"label": "Username (handle)",
"type": "text",
"placeholder": "Defaults to the part before @"
}
]
},
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Set a new password for an existing account. Stoat hashes it, so the account stays usable.",
"icon": "🔑",
"fields": [
{
"name": "username",
"label": "Username or email",
"type": "text",
"required": true
},
{
"name": "password",
"label": "New password",
"type": "password",
"placeholder": "Leave blank to generate"
}
]
},
{
"id": "list_users",
"category": "users",
"label": "List Users",
"description": "Every Stoat account, with its email and whether it is disabled.",
"icon": "📋",
"fields": []
},
{
"id": "delete_user",
"category": "users",
"label": "Disable User Account",
"description": "Block sign-in without deleting the account or its messages. Reversible.",
"icon": "🚫",
"destructive": true,
"confirm": "The user will not be able to sign in again until re-enabled.",
"fields": [
{
"name": "username",
"label": "Username or email",
"type": "text",
"required": true
}
]
},
{
"id": "enable_user",
"category": "users",
"label": "Enable User Account",
"description": "Undo a disable and let the account sign in again.",
"icon": "✅",
"fields": [
{
"name": "username",
"label": "Username or email",
"type": "text",
"required": true
}
]
}
]
}