Skip to content

Authentication and accounts

Every server call except registration, login, and the bootstrap claim runs behind authentication. The server verifies a bearer token, resolves it to a user, and authorizes the call against that user’s role.

One account holds the identity of one person:

Field Meaning
username Unique, 1–30 characters, chosen at registration
email Unique, used as a login identifier
password_hash bcrypt hash; NULL means the account has never been claimed
role Exactly one role, guest by default
timezone IANA timezone such as Europe/Bratislava, used to display temporal values
phone_country ISO 3166-1 alpha-2 country such as SK, used to interpret national phone numbers

The server stores only a bcrypt hash, never the password. Hashing and verification run on a small bounded worker pool, so password processing has a hard concurrency ceiling and can refuse work when it is saturated.

Register creates an account on the guest role. The role is never taken from the request, because letting a caller choose their own role would let anyone register as an administrator. Every new account therefore starts with nothing and receives permissions only through an administrative act.

Registration accepts a username, an email, a password, a timezone, and a phone country. The username and email must be different, the password must match its confirmation, and the identifiers must not already be in use.

Login accepts either the username or the email as the identifier. A successful login returns:

  • an access token and its type (Bearer);
  • the user id, username, and role;
  • the user’s timezone and phone country;
  • an authorization snapshot listing every permission the role currently holds.

The snapshot is computed at login time. A token keeps working until it expires even if grants change afterward; see Roles and permissions for how changes take effect.

Both login and registration are rate-limited per source address: 30 login attempts and 5 registrations per 20-second window. A caller that exceeds a limit receives a resource-exhausted error.

A successful login mints a signed JWT:

  • algorithm: HS256, signed with the JWT_SECRET environment value;
  • claims: sub (the user id), iat, and exp;
  • lifetime: 24 hours.

Tokens are stateless. The server keeps no session table and does not validate tokens against the database on every call; it only verifies the signature and expiry.

Clients send the token in every request header:

Authorization: Bearer <token>

Every gRPC service except AuthService itself requires this header. AuthService must stay reachable without a token because registration and login are how a caller obtains one; its administrative calls authenticate themselves internally.

A fresh database ships with two accounts that have no password: superadmin and admin. An account without a password cannot log in. The only call it accepts is SetInitialPassword, which sets a password once and turns the account into an ordinary one.

The password_hash IS NULL guard makes the call a one-time claim rather than a password reset: an account that already has a password refuses it.

GetAuthorization returns the caller’s role and its effective permission snapshot without requiring a fresh login. SetTimezone updates the caller’s stored timezone so temporal values are rendered in the right zone.

Roles and permissions explains the two-plane permission model: structural versus data roles, grantable objects, role inheritance, and the audit log.