Skip to content

Product · Access

Unticking a box has to change what happens.

Access here is a list of 34 named permissions that lives in the source code, and a role is a selection from it. The interesting part is not the list. It is what stands behind each entry, because for a while ten of them stood in front of nothing.

34 permissions in 7 groupsThree system roles, editable by nobodyThe organization comes from the token, not the request

The catalogue

The list is a source file.

34 permissions, in 7 groups, written down in the code rather than kept in a table. That has consequences you can actually see: a new permission arrives with the feature it gates instead of as a data change that has to be applied to every account separately, and there is exactly one copy of the list to read.

The role editor in the portal is drawn from that same list, served by the server that enforces it. So the words beside the checkbox (“Play back call recordings”, “Stop an in-progress recording”, “View the whole organization’s call history”) are the words in the code, not a friendlier restatement of them written months later.

A caller’s permissions are worked out as each request arrives, from the role their account currently points at. Which means an edit to a role is in force on the very next request, rather than whenever the people holding it happen to sign in again.

Custom role34 permissions
Calls10
Contacts2
Messaging2
Voicemail2
People & access4
Organization12
Emergency calling2

The groups and their sizes as the role editor lists them. A custom role is a selection out of this and nothing else.

Enforcement

A box that changes nothing is worse than no box.

Ten of these permissions were in the editor and grantable for months while being enforced nowhere. The message routes and the voicemail routes carried no check at all, so an administrator who unticked “Send SMS” for a role changed nothing: anybody signed in could read the organization’s whole message history, or delete any voicemail in it.

That was never one customer reaching another’s data. Every query was already scoped to its owner, and a suite of its own proves that. It was the distance between what the editor promised and what the server did, and that is a hard defect to notice, because a route with no gate answers exactly like a route with one for a caller who happens to hold everything.

Which is why the answer was not a fix but a habit. Each of those permissions now has a test that gives a role exactly one of them and checks both directions: the route it should reach, and the neighbouring route it should not. Checking only the refusal would pass just as well against a server that refuses everything, which is the sort of test that makes a gap feel closed.

Switching a check on is its own risk, and it was treated as one. Before those routes began refusing anybody, a read-only report went over the roles that actually existed to say which of them would lose access the moment enforcement started. A permission nobody had thought to tick, suddenly enforced, takes something away from somebody’s Friday afternoon.

And when a check does refuse, it says which permission was missing rather than only that something was. Somebody working out why a role cannot reach a screen needs the name of the box to tick, and anyone who can edit roles can read the whole list anyway.

Roles

Three you cannot edit, and as many as you like that you can.

Every organization starts with the three beside this, and the server refuses to change or delete any of them. The reason is narrow and real: editing them would let somebody strip the permissions that grant access to the role screen (off the role they are currently holding), and there is no way back in from there.

What a system role grants is read from the code at the moment of the decision, not from the copy stored beside it in the database. The stored copy is kept in step for anything reading the tables directly, but it never gets a vote. So a stale row, or one edited by hand, cannot quietly widen Supervisor or narrow Agent.

Roles of your own are a selection from the same catalogue and nothing else. A key that is not in it is refused when the role is saved rather than stored and ignored, which would be the same defect as the ten, wearing different clothes.

A custom role that people hold cannot be deleted out from under them. You are told how many hold it and asked to move them first, because deleting it would change what those people can do without anybody deciding that it should.

One thing a role deliberately does not decide: which queues somebody is in. A role says whether you may create a queue and choose who is in it. Being in one is a property of the person, kept next to them rather than inside their role, so two people with identical permissions can be answering entirely different lines.

How a waiting call finds them

System role

Tenant Admin

Full access to every feature, including billing, roles, and organization settings. Defined as the whole catalogue rather than as a list of keys, so something shipped next month is reachable by an administrator the day it lands, with no migration run against your account to grant it.

System role

Supervisor

Handles calls and oversees the team, without access to billing or organization settings. Read-only on call flows, and that line is drawn on purpose: somebody fielding "why did that call go to voicemail?" has to be able to see the routing, while changing where a company's calls go is an administrator's decision.

System role

Agent

Handles calls, messages, and contacts. The set somebody needs to work a phone at all. It includes seeing the directory, because the transfer and add-a-colleague pickers are built from it and a role without it offers an empty list instead of an error.

The last admin

The edit that would lock everyone out is refused.

Take “Invite, edit, and deactivate agents” and “Create and edit roles” away from the role your only administrator holds, and nobody in the company can administer anything again: not invite an administrator back, not edit a role, not reach billing. Getting out of that means us going into the database for you, which is not a thing a customer can do for themselves.

Which is why the check is not a warning on a form. It sits in the service every one of those paths has to go through: demoting somebody, deactivating them, moving them to another role, deleting a role, and editing the permissions on one. A form can be the only thing standing in the way of exactly one screen; the service is the thing all five have in common.

The permission edit is the awkward case and is checked on its own terms. Demoting one person leaves everyone else on their role untouched; unticking a box demotes every holder of that role at the same moment, so none of them can be the reason the change is safe. The check therefore looks for an administrator somewhere other than the role being edited.

The refusal names the way out rather than just saying no: give another agent an administrator role first, or keep one of those two permissions on this one.

When a permission edit does go through, everybody holding that role has to sign in again. Authorization changed the instant the row was written; the sign-in is to make sure nobody is still working from a list their software cached when they started their shift.

Whose data

Which organization you are is not something a request gets to say.

Every customer shares one database, and every row belonging to a customer carries the id of the organization that owns it. That id is worked out on the server from the verified session token, and a route handler never reads one out of a request body or a query string. There is no field to tamper with, because there is no field.

A second guard sits behind that one, and it is there because of how the data layer behaves rather than because of anything a caller could send: a filter that is missing is treated as no filter. So a request that somehow arrived without an organization on it is refused outright, instead of being allowed to run a query with the condition quietly dropped.

And there is a suite whose only job is to try it on: one organization’s session against every route the other organization’s data sits behind, in one pass, asserting that all of it fails. It also asserts how it fails: as no such record, rather than as not allowed. The second answer would confirm the record exists.

Sessions for customers and sessions for the console we administer customers from are signed with different keys. Deliberately, not tidily: because a missing condition means no condition, a validly signed token with no account named on it would otherwise match whichever account came first. Separate keys mean such a token fails at the signature and never reaches the code that would have had to notice. The second key is derived from the first rather than added as another setting, so there is no way to deploy without it.

The same ground, written for a security review

Not a role

Some of it is the organization’s decision, not the role’s.

A role is the wrong shape for two of these questions, and using it anyway is how a permission model becomes a place where the answer depends on which screen you opened.

Recording is the clear case. Whether calls are recorded at all is one setting for the whole organization (always, the agent’s choice, or never), with a second switch for whether an agent may stop one that is already running. The role decides who may press record and, separately, who may play a recording back afterwards. The policy decides whether pressing it was ever on the table.

What that looks like during a call

Your own presence

Not permissioned at all. Everybody sets their own.

Somebody else’s

A permission, and only consulted when the row is not yours, which is the version of the check that matches what a supervisor is actually doing when they mark a colleague away.

Your own call history

A separate permission from the organization’s. One of the few places the catalogue draws a line between mine and everyone’s.

What this does not do

People sign in with an address and a password kept here. There is nothing to connect to whatever your company already uses to let people in, so an account on Ringfully is one somebody creates here and removes here. And removing it is a thing to remember when a person leaves, because nothing outside will do it for you.

A permission means the same thing everywhere it applies. Apart from your own call history against the organization’s, there is no narrower slice of the data to hand out: a role that can see contacts sees the whole book, not one team’s corner of it. If what you need is a role that only reaches part of the company, that is a real gap rather than a setting we have hidden.

Nothing here is temporary. A permission stays granted until somebody unticks it; there is no grant that lapses on its own on Friday, and nothing asks a second administrator to approve the change before it takes effect.

And roles belong to one organization. There is no group directory somewhere else to take membership from, and no role written once and pushed to several accounts: who holds what is set in the portal, by a person, per company.

If one of those is the thing your review will actually stop on, say which in the first email. I would rather tell you where it really stands than have you find out on the second call.