Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.
Knowledge base

Things that cost me a day, so they cost you none

Working notes from building Invision Community and Flarum applications. Mostly the failures that give no error at all — the ones where everything installs cleanly and quietly does the wrong thing.

92 articles

Invision Community 5

86 articles

Extensions and contracts

39

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

Languages and text

5

The string table, translation, and the places where text does not appear where you expected it to.

Theming, templates and forms

9

Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.

Background work and scheduled tasks

5

The queue system, work that has to happen after the response, and jobs that finish without doing anything.

Data, settings and storage

11

The database layer, settings, tags, file storage, and backing up a live site.

AI features and expectations

5

What these features do, what they cost, and what buyers reasonably but wrongly assume they do.

Application structure and releases

11

The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.

Realtime, chat and calls

1

WebSocket gateways, relays and the server-side pieces live features depend on — where "it works when I test it" and "it works for your members" are different claims.

Nothing matches that.

"You do not have permission for that" on your own AdminCP screen: the $csrfProtected property

You add an AdminCP controller. The list screen works. Then you click Add, or Edit, or a date-range link on your own report, and Invision Community answers:

Sorry, you do not have permission for that!

You are logged in as a root administrator. Nothing is wrong with your ACP restrictions, and that is exactly where the message sends you to look.

The cause is CSRF, not permissions

IPS\Dispatcher\Admin runs this before dispatching:

if ( !isset( $this->classname::$csrfProtected )
     and array_diff( array_keys( Request::i()->url()->queryString ),
                     array( 'app', 'module', 'controller', 'id' ) ) )
{
    Session::i()->csrfCheck();
}

Read it carefully. Any AdminCP URL carrying a query parameter other than app, module, controller or id is CSRF-checked automatically — unless the controller class declares $csrfProtected. And a failed CSRF check reports HTTP 403 with the language string admin_csrf_error, which renders as the permission message above.

So this is fine:

app=myapp&module=main&controller=things          ← works
app=myapp&module=main&controller=things&id=4     ← works, id is allowed

and every one of these 403s:

...&controller=things&do=form                     ← "do" is not on the list
...&controller=things&do=settings
...&controller=report&days=90                    ← your own filter links
...&controller=things&page=2

The fix

Declare the property. Its value is irrelevant — the dispatcher only calls isset() on it. It means "this controller takes responsibility for its own CSRF checks".

class things extends \IPS\Dispatcher\Controller
{
    public static bool $csrfProtected = TRUE;

    // ...
}

Then call Session::i()->csrfCheck() yourself on the methods that actually change something — delete, toggle, reorder — and link to those with ->csrf() on the URL. Read-only screens such as a form or a report do not need a token.

Invision Community's own code declares this on essentially every AdminCP controller it ships. It is easy to miss precisely because it is boilerplate that nothing reminds you about.

Why nothing catches this before release

The failure is invisible to every check short of clicking the link:

  • The app installs cleanly.
  • The menu entry appears.
  • The default screen — no extra query parameters — renders perfectly.
  • Rendering the form's fields in a test harness works, because the harness never goes through the dispatcher.
  • Nothing is written to any log.

Only a real HTTP request to a real do= URL as a signed-in administrator reveals it. If you have a screen-rendering test, make it fetch the actual AdminCP URLs rather than constructing the form objects.

A trap while you are testing this

Do not "fix" it by appending csrfKey to the URL. In developer mode Output::sendOutput() deliberately fatals on any 2xx response whose URL still contains a CSRF key:

An 200 response is being sent however the CSRF key is present in the
requested URL. CSRF keys should be sent via POST or the request should be
redirected to a URL not containing a CSRF key once finished.

So without the property you get a 403, and with a token in the URL you get a 500. Both symptoms point away from the one-line cause.


User Feedback

Recommended Comments

There are no comments to display.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.