Access & security

Deckgauge authenticates through Keycloak, controls access per board with three roles, and never writes back to your source-of-truth trackers.

Authentication

Sign-in is entirely Keycloak's job — Deckgauge never sees or stores a password. The web app authenticates via OIDC (NextAuth); the API independently verifies every request's JWT against Keycloak's public signing keys (JWKS) before trusting it. On first sight of a valid token, the API creates or updates a local User row keyed on the token's sub claim, filling in email and name from the token — so your local Deckgauge identity always tracks your Keycloak one, never the other way around. Two realm roles, each read straight from the token's roles and not something you can grant yourself, gate everything below that isn't a board or org-tree membership: an admin role (configurable, defaults to cockpit-admin, env COCKPIT_ADMIN_ROLE) and an analytics role (configurable, defaults to cockpit-analytics, env COCKPIT_ANALYTICS_ROLE). Holding the admin role always satisfies the analytics check too, so an instance that upgrades onto this role doesn't lose every analytics page the moment it ships, before anyone has been granted cockpit-analytics directly — but the reverse doesn't hold: an analytics-only account is not an admin. Both variables are documented in the Configuration reference; what each role grants is covered below. For how someone gets either role in the first place — the first-run bootstrap, upgrading into this model, granting a realm role via kcadm.sh, and recovering an instance with no admin at all — see Administration.

The shipped realm has self-registration turned on, so anyone who reaches your Deckgauge login page can create their own account via "Create an account." For a closed instance, turn this off in Keycloak's realm settings under Login → User registration — do it before the realm is first imported if you're editing the shipped config file, since it's only read once, on the realm's first boot.

Board roles

Access is granted per board at one of three levels, ranked Viewer < Editor < Owner:

RoleCan do
ViewerRead the board — its groups, rows, columns, and statuses — its dashboards, and its roadmap view.
EditorEverything a Viewer can, plus create, change, and delete rows, groups, columns, statuses, comments, uploads, and automations; edit the board's column layout and hidden fields; and edit a roadmap's schedule and settings.
OwnerEverything an Editor can, plus grant, change, or revoke other people's access to the board, and delete the board itself.
  • Every action above, including reading the board's own contents, is enforced server-side independent of the UI: each route declares which role it requires, the API refuses to start if any route doesn't declare one, and a test refuses to pass if a declared route sits outside the code path that does the checking. A request from someone below the required role gets 403; a request with no valid session at all gets 401. This holds however you reach the board — a comparison or a cross-board roadmap re-checks your role on each board it pulls from, every time it is read, so access you lose stops applying immediately rather than at the next reload.
  • What this does not cover is the engineering-intelligence and timesheet analytics built from your connected Jira/GitHub/GitLab/ADO sources: engineering intelligence is gated by the cockpit-analytics realm role alone, and timesheets by that same role plus Viewer on the org tree the request names — see the "Org tree roles" and "Analytics" sections below.
  • A board must always keep at least one Owner. Trying to revoke the last one is refused; roadmaps have the same last-owner protection independently.
  • A DECKGAUGE_SINGLE_USER=true instance bypasses all of this — every request is allowed regardless of role, or of being signed in at all. See Configuration reference.

Org tree roles

Org trees — the reporting hierarchies behind engineering intelligence and timesheets — are access-controlled the same way boards are, at one of three per-tree levels ranked Viewer < Editor < Owner. An employee board hangs off its parent tree and inherits that same role check; there's no separate role to grant on the employee board itself.

RoleCan do
ViewerRead the tree — its employees, comments, and activity — and the employee boards under it, including fields like email, phone, and the ranking/leaderboard data behind the Rating column.
EditorEverything a Viewer can, plus import or sync employees from a source, create/edit/delete employees, comments, and aliases, re-parent an employee's manager, and edit employee-board groups, columns, and field values.
OwnerEverything an Editor can, plus rename or delete the tree, manage its source connection, and grant, change, or revoke other people's access to the tree.
  • Enforced server-side the same way board roles are, down to the same 403/401 split. Salary is the one exception: it's a separate, longer-standing check unrelated to board or org-tree role — the API omits it from the response, and refuses to write it, unless the caller's token carries the admin realm role.
  • Sharing works the same way as a board's: an Owner opens the tree and uses its Share control to search for a registered user, add them at Viewer, Editor, or Owner, change an existing person's role, or revoke access. Like boards, a tree must always keep at least one Owner — removing the last one is refused with a 409.
  • An admin is Owner-equivalent on every org tree, without needing an access row at all: admin-ness lives in the Keycloak token, not in Postgres, so it's checked as a rule at evaluation time rather than backfilled as data. This is what keeps an admin from being locked out of a tree they were never explicitly granted access to.
  • The one deploy-time gotcha: access starts empty. This role model replaces a prior "any signed-in user can read and edit" check, and there is no record of who used to informally rely on that — so on the release that ships this, every org tree that already existed has zero access rows, and every non-admin loses read and write access to every existing tree until an owner or admin shares it with them again. (A tree created after this ships stamps its creator as Owner automatically, so this only bites pre-existing trees.) If no account holds the admin realm role yet either, the documented recovery path is the grant:org-access operator script — see the Configuration reference — rather than hand-written SQL against the database.

Analytics: the cockpit-analytics realm role

Engineering intelligence and timesheets are governed by a second realm role, independent of the tree roles above.

  • Engineering intelligence — the dashboards, developer tables, anomaly, and coverage views built from your connected sources — requires only the cockpit-analytics realm role. Those routes carry no org-tree id in the request, so there is nothing to scope them to; holding the role is the whole check.
  • Timesheets — any timesheet read that names a specific org tree in its request (today: the hours grid, CAPEX/OPEX report, epic breakdown, and interval history) requires both cockpit-analytics and Viewer on that tree: holding the analytics role alone is not enough to read a tree's hours if you aren't also on that tree's access list. The one timesheet read with no tree in its request — the in-progress status-rules configuration — needs only the analytics role.
  • An admin's token satisfies cockpit-analytics automatically, even without holding that role directly — see Authentication above. The reverse does not hold: holding cockpit-analytics does not make you an admin, and grants neither salary access nor Owner-equivalence on org trees.

Beyond boards and org trees: comparisons and connections

Not everything follows the Viewer/Editor/Owner model above — some resources use a simpler check, or no per-resource role at all:

  • Comparisons are creator-only — opening, editing, or deleting one requires being the user who created it; there's no sharing or per-user membership. If you could previously open a comparison someone else created, you no longer can. Adding a board to a comparison also requires Viewer on that board, and a comparison stops rendering if you lose access to one of its boards — remove it and the rest keeps working. See Comparing boards side by side.
  • Provider connections (Jira, GitHub, GitLab, Azure DevOps instances) can be created by any signed-in user, but only the creator can change or delete one afterward. A connection created before this model existed has no owner yet — it becomes owned by whoever edits it first. That first-edit rule buys attribution, not prevention: the edit it allows through is deliberately harmless, so that once it happens there is always a named owner accountable for anything more sensitive done afterward — it does not stop that owner from later doing something destructive with the connection, only guarantees no unattributed caller can. Two things are held back until an owner exists, because both are destructive and a still-unowned connection is one nobody would be accountable for: deleting the connection (which cascades away the source configuration of every board using it), and changing its host or organisation URL (which would point the stored credential at a different server). Both are refused on an unclaimed connection with a 403. For Jira, GitHub, and Azure DevOps, claim it first with any other edit — renaming it, or replacing its token from the Sources tab — and then, as its owner, you can do either. GitLab has no such edit: there is no rename/update route for a GitLab instance, so its only claim path is replacing its token, and that swap is itself refused unless the new token authenticates successfully. An unclaimed GitLab connection whose stored token has already stopped working therefore cannot be claimed by anyone — and so cannot be deleted or repointed either, until a fresh working token is supplied.
  • A sync's board-facing settings are a narrower case, and inconsistent today: deleting a Jira/GitHub/GitLab/Azure DevOps project sync is gated on Editor over every board it's attached to, because the delete cascades away that board's whole source configuration. Editing one is notPATCH /project-syncs/{jira,ado,gitlab}/:id (the JQL filter or status-category mapping controlling what a board pulls in) only requires being signed in, so today any signed-in user can rewrite that configuration for a sync attached to a board they hold no role on at all.
  • Standalone roadmaps (the cross-board roadmaps list, distinct from a board's own roadmap view) keep their existing Viewer/Editor/Owner-style RoadmapAccess roles. A roadmap role is not a board role, though, and the two are granted separately: subscribing a board or adding one of its groups requires Viewer on that board, editing an item's schedule or fields requires Editor on the board it lives on, and a roadmap shows you only the groups whose boards you can see. So a roadmap shared more widely than its boards shows each person their own readable slice of it.

Sharing

Board owners manage who has access from the board's share controls — search for a registered user, add them at Viewer, Editor, or Owner level, change an existing person's role, or revoke access entirely. See Owners & sharing for the full walkthrough.

Data handling & privacy

  • Source available: the app, its databases, and its analytics store (ClickHouse) are all open and inspectable. Deckgauge doesn't phone home with your data.
  • Read-only connectors: Deckgauge only reads from Jira, GitHub, GitLab, and Azure DevOps — it never writes back.
  • Secret masking: stored tokens are never returned in full — the API sends back *** in place of the real value for Jira, GitHub, and ADO; for GitLab it omits the token field from the response entirely.
  • Least privilege: connect each source with a token scoped to only what Deckgauge needs to read.
Measuring peopleContribution ranking and per-person timesheets are sensitive. Default to team/aggregate views, treat individual figures as decision support with a human in the loop, and check local requirements (e.g. works-council / employee-monitoring rules) before rolling out per-person metrics.

Related

  • Administration — how someone becomes an admin, upgrading into this access model, and recovering an instance with no admin.
  • Keycloak setup — configuring the identity provider itself, including federating to a corporate IdP.
  • Your account & session — signing in, the user menu, and session expiry.
  • Owners & sharing — the share-board UI in depth.

Last updated