A demo that only shows screenshots convinces nobody. We wanted visitors to open the real OpsAPI admin dashboard and click around. That means publishing a password, and three things can go wrong: visitors see other people's data, visitors change things, and visitors lock the next visitor out.

1. Keep real people out of view

The apps in the demo let anyone sign in with Google, and those accounts go to a workspace called demo. If the published login could read that workspace, its user list would show the real email address of everyone who had tried the apps. So the published login lives somewhere else: a showcase workspace holding only fictional example.com users, owned by a fictional user too, so not even the owner's address is real.

The public demo login can read the showcase workspace only; real sign-ins land in a private workspacePublic visitordemo loginReal personown Google accountRole: viewerread on every moduleno create, update, deleteRole: memberdefault for sign-upsno admin rights403SHOWCASEfictional example.com usersfictional owneremailed code: offDEMOreal people's accountsreal owneremailed code: on
Workspaces are the boundary. A member of showcase asking for demo's members gets 403, whatever header it sends.

2. Let visitors see everything and change nothing

OpsAPI's roles are lists of module.action permissions per workspace. The seeded owner role has manage on every module; the demo login gets a viewer role built from the same module list with only read:

-- seed-showcase.lua
local read = {}
for module in pairs(cjson.decode(owner_role.permissions)) do read[module] = { "read" } end
-- => {"users":["read"],"roles":["read"],"settings":["read"], ...}

Every page in the dashboard opens; every write is refused by the permission middleware before it reaches a query:

POST /api/v2/users   (as demo@opsapi.org in showcase)
403 {"error":"Permission denied","required":{"module":"users","action":"create"}}

3. Survive visitors who try anyway

Some actions belong to every signed-in user, whatever their role: changing your own password or deactivating your own account. A visitor could use either to lock everyone else out. A CronJob puts the account back every 30 minutes: the password hash of the published password, the active flag, and the viewer role.

A CronJob restores the demo account every 30 minutesVisitorchanges passwordor deactivatesusers tablepassword hashactive flagviewer roleCronJob*/30 * * * *psql reset.sqlrestoreworst case: 30 minutes of a changed password
The reset only touches the demo account and the showcase workspace's sign-in setting.

4. No emailed code, in one workspace only

Members are asked for an emailed code when any workspace they belong to requires one. Nobody can read the demo account's inbox, so showcase has the code switched off. The private demo workspace keeps it on, and platform admins are always asked regardless.

Checklist

RiskControlHow we tested it
Visitors see real peopleSeparate showcase workspace, fictional ownerMember list over the public URL shows only example.com addresses
Visitors read the private workspaceMembership check per workspaceRequest with X-Namespace-Slug: demo returns 403
Visitors change dataviewer role: read onlyCreating a user returns 403 Permission denied
Visitors lock the account30-minute reset CronJobRan the job by hand; the published password signs in afterwards
Inbox nobody readsEmailed code off in showcase onlySign-in returns a token with no 2FA step

One edge lesson

The first version failed every read with "Access denied to this namespace". The edge proxy had been set to add X-Namespace-Slug: demo to every request for the identity host, which overwrote the header the dashboard sent and pinned every call to the private workspace. Tenancy belongs to the client and the token, not to a proxy rule; removing the header fixed it.

Try it on the live demo.