Skip to content

painfree 0.8.0

Latest

Choose a tag to compare

@reyemb reyemb released this 04 Sep 15:07

A token's scope claim no longer nullifies a grant when it names none of this service's scopes.

Privilege is the union of a subject's grants, intersected with the scopes the token asked for. PAINFREE_OIDC_SCOPE_CLAIM defaults to scope, which is OIDC's own claim, and a compliant provider fills it with openid profile email. None of those is a scope of this service, so the intersection was with the empty set: a correct operator grant, a valid sign-in, and a caller holding nothing — with no line in the log stream saying which of the two decided it.

The intersection now applies only when the claim names at least one scope this service knows. A deployment that put this service's scope names into its provider narrows exactly as before; one that never asked to narrow is repaired. Nobody's privilege moves except back towards the grant they were given, which is what makes the change safe to take. Narrowing that does happen is now recorded rather than inferred: auth.scopes_narrowed names the surviving scopes, and the console says a token can narrow a grant.

If you had to create client scopes in your provider named after this service's privileges to make grants work, that was the workaround for this. You can remove them.

The four operator pages

Schedules lead with coverage. The failure this page exists to catch is a silent one: a run that finds nothing is a success, so a schedule whose window never advances stays healthy run after run while the days behind it pile up. That was a line of text in the seventh of eight columns. It is now a band per schedule — covered solid, outstanding hatched, not-yet-started in its own neutral state — drawn across the window the next run will ask for. Health moved to the row's leading edge and the BTF triplet folded into the schedule's own cell; eight columns became four.

Access is a matrix. People down, connections across, the level in the cell, so who can move money at this bank and what does this person hold are each one read. An oversight grant names no connection, so it spans the columns rather than sitting in one, and a column header links to that connection's own page. The page also says how each person signs in: a grant is inert without a way to use it, and a subject granted before their account exists could never sign in while looking exactly like a working grant.

Deleting an account is a page. Every lever used to be live on screen — four forms per row, with Delete beside Set password. The levers now sit behind a per-row disclosure with the two that stop somebody signing in separated and labelled, and deleting asks on a page of its own that names what survives it: the grants stay, so an account remade under the same name comes back holding what it held. Under an identity provider, the local accounts that authentication already refuses are named.

The audit trail reads as actions. One operator action writes several rows, and they already share a request_id. Consecutive rows sharing one are drawn under a single heading; three quick filters became links with the full form behind them, and the correlation ids sit under the link they belong to.

No migration, no new stored field, and no change to any contract.