crabidy/docs/src/auth.md

5.1 KiB

Roles and authorization

By default the server is open: it listens on the network and anyone who can reach the port has full control — not only playback, but library writes such as renaming or deleting your saves. This is the right default on a trusted home network with no config to write.

Adding password hashes to the [auth] section of crabidy-server.toml locks the server down from the top. Each password you set lowers what a caller with no credentials may do; a caller with a matching password is elevated to that role. You lock the powerful roles first and leave the weaker ones open for anyone on your network — see What anonymous callers get below.

The three roles

Roles are ordered by privilege; each includes the rights of the ones below it (owner ⊃ queue-owner ⊃ queue-appender):

  • owner — the normal user: everything, including all library writes.
  • queue-owner — anything on the queue and playback (append, remove, reorder, clear, shuffle, repeat, play/stop, next/prev, volume, mute, …), but no library writes: no bookmarks (w), no captures (W), no saving, renaming, or deleting.
  • queue-appender — may browse and search the library and append tracks to the queue; nothing else. (Searching is allowed because appending something first means finding it.)

Turning it on

Each role is credentialed by one argon2id password hash in PHC format. Generate a hash and store it in the config in one step with:

crabidy-server guard <role>

where <role> is owner, queue-owner, or queue-appender. This reads a password, hashes it (argon2id), and writes the resulting PHC string into crabidy-server.toml's [auth] table. The file ends up like this:

[auth]
# One PHC hash per role. Guard from the top down.
owner = "$argon2id$v=19$m=19456,t=2,p=1$..."
queue_owner = "$argon2id$v=19$..."
queue_appender = "$argon2id$v=19$..."

A role's PHC hash — not its password — is what lives in the file. Omit a role's key to leave that role open: no password is needed to act as it, and no one can log in as it.

What anonymous callers get

A request with no credentials is not rejected — it is granted the most powerful role you did not guard. Setting a password walks that floor down one step:

Guarded roles A no-credential caller is…
(none — no [auth]) owner — the open default
owner queue-owner
owner + queue_owner queue-appender
all three nothing — credentials required

So to run a server where guests may append but a password is needed to run the queue, guard owner and queue_owner and leave queue_appender open.

Because the anonymous caller always gets the highest open role, guarding a weaker role while a stronger one is still open would be pointless — the anonymous role would simply outrank it. Such a config is treated as a mistake: setting queue_owner without owner, or queue_appender without queue_owner, aborts startup, and crabidy-server guard refuses to write it. Guard the roles from the top down.

How enforcement behaves

  • Fail-closed. Authorization is enforced in one place, in front of the RPC handlers, and it is default-deny: an unknown or future method requires the owner role until it is explicitly mapped to a lower one. No handler ever sees an unauthorized request.
  • Startup is strict. A crabidy-server.toml that exists but does not parse — or that guards the roles out of order — aborts server startup rather than silently running open: a broken auth config never downgrades to a weaker posture than intended.
  • Clients send the role as the username. A client authenticates by sending the role name (owner, queue-owner, queue-appender) as the basic-auth user and the role's password as the basic-auth password. Set these with the client auth subcommand or in the client config's [server] table (see Configuration). With no credentials configured the client sends no header and is treated as the anonymous role — which keeps the zero-config local setup working against an open server.
  • A caller below a method's required role — whether anonymous or logged in — gets gRPC PERMISSION_DENIED. A wrong password (and an anonymous request to a fully-locked server) gets UNAUTHENTICATED, with every authentication failure answering identically so a caller cannot probe which part was wrong. A wrong password is never quietly downgraded to the anonymous role.
The transport is plain HTTP/2 — basic auth travels in the clear. This is
fine on a trusted LAN. Put TLS in front of anything exposed beyond it (a
reverse proxy or a VPN); crabidy does not terminate TLS itself.

Secrets — client passwords, the authorization header, and stream tokens — are never written to logs, traces, or error messages.

See also