moonfire-nvr icon indicating copy to clipboard operation
moonfire-nvr copied to clipboard

user authentication

Open scottlamb opened this issue 6 years ago • 5 comments

Current status: functional but some features missing:

  • [x] schema for users and sessions
  • [x] moonfire-nvr config for users
  • [x] API endpoints to log in and out
  • [x] check authentication status on API requests
  • [x] moonfire-nvr login command to make a new session from the CLI (mostly for tools to use)
  • [x] add the Varies: cookie or whatever headers to ensure authenticated and unauthenticated requests don't get mixed up in caches.
  • [x] document how to set up a nginx proxy properly. (I have it working on my setup, though.)
  • [x] consider tighter security (__Host + SameSite=strict) when using tls. currently it uses Secure; SameSite=Lax. (Update: I'm happy with the status quo.)
  • [x] add an endpoint that shows what the auth request looks like, to ensure the nginx configuration + --trust-forward-hdrs are working together properly.
  • [ ] add ability to invalidate existing sessions.
  • [ ] add endpoint to see existing sessions associated with an account + use in JS
  • [x] add a password change endpoint + use in JS
  • [ ] add some restriction on when insecure cookies can be served. They're useful for development with a WebPack proxy server (yarn start) but shouldn't ever be served in production. Maybe restrict via the Host header.

Original issue text:

I want proper user authentication; this (along with some manner of https) is a precondition of exposing Moonfire NVR to the Internet.

As @dolfs said, a temporary measure could be to do basic HTTP with a apache/nginx frontend. But I think it shouldn't be too bad to do a proper built-in thing.

Here's my proposal.

https is a prerequisite. (either built-in or through a proxy.)

You can create/delete/modify users through moonfire-nvr config.

Creating a session could be simply password-based. Some things that I consider out of scope:

  • two-factor authentication (maybe later)
  • email-based password recovery (maybe later)
  • web-based password changes (later)
  • OAuth2 (probably never; I want it to work without an Internet connection)
  • protection against online brute force attacks: rate limiting and/or auto password disable after too many failed guesses (maybe later; you've gotta pick a good password for now)
  • even logging of auth failures (likewise; pick a really good password for now)
  • roles / permissions (for now, the web interface is read-only, and everyone can read everything)
  • user/device settings, such as UI preferences stored on the server side (maybe later)

Sessions could simply be a long random number, stored as a hash for the same reason people hash passwords: so a database leak doesn't trivially compromise the credentials. (They should need to edit the database for to gain access, which may be harder to do than getting an old backup, and which leaves more evidence behind.)

Straw man schema:


create table users (
  id integer primary key,
  username unique not null,

  -- If set, a hash for password authentication, as generated by `libpasta::hash_password`.
  password_hash text,
  password_id integer not null default 0,  -- increasing with password sets/clears.
  password_failure_count integer not null,  -- updated lazily; reset when password_id incremented.

  -- If set, a Unix UID that is accepted for authentication when using HTTP over
  -- a Unix domain socket. (Additionally, the UID running Moonfire NVR can authenticate
  -- as anyone; there's no point in trying to do otherwise.) This might be an easy
  -- bootstrap method once configuration happens through a web UI rather than text UI.
  unix_uid integer
);

create table user_sessions (
  user_id integer references user (id),
  hashed_session_id blob unique not null,  -- hash function TBD
  creation_password_id integer not null, -- the id it was created from
  creation_peer_addr blob not null,  -- IPv4 or IPv6 address
  creation_time integer not null,  -- sec since epoch
  creation_user_agent text,
  revocation_time integer, -- likewise
  description text, -- probably a device name
  last_use_time integer,  -- updated lazily on flush
  use_count not null -- likewise
);

Typical sessions should probably be represented as cookies with the HttpOnly flag set (meaning not accessible to Javascript). If a hit on /api/... lacks the cookie, it should return a HTTP 401 which the Javascript knows means to redirect the browser to /login, a simple HTML login form which on success sets the cookie and redirects back.

The cookies should also be secure (meaning only sent over https, not http).

There should also be a way of getting a session id for use by some automated thing. Example: a program that dumps events into the database based on my Elk security system's zone status. Something you can paste into its config file. Better to have separate, high-entropy credentials where possible so you can see what was compromised and disable it independently of the others.

scottlamb avatar Mar 09 '18 07:03 scottlamb

There should also be a solution for mobile devices using the API. The standard web based approach for that is cumbersome. Consider supporting oauth based approach. Tokens handed out should perhaps be revokable through the web interface, but should otherwise be fairly long term expiration, or we need to also implement the token refresh portion.

dolfs avatar Mar 13 '18 07:03 dolfs

Finally getting there. Some remaining things to do:

[moved to first comment to make the issue more skimmable]

It'll be a lot more slick once we support built-in https (see #27), but I think if we get those checkboxes ticked we can reasonably say it has auth support.

scottlamb avatar Dec 01 '18 08:12 scottlamb

I still don't have all the checkboxes ticked, but it works okay for me, and it shouldn't urgently require schema changes, so I merged it to master.

scottlamb avatar Dec 27 '18 22:12 scottlamb

Possible enhancement - assign roles to users. At a minimum this would be admin vs user, where an admin can do everything, a user can only view. This would simply need a boolean added to the user table.

More fine-grained roles would be nice. This could be achieved with a minimal change to the schema by adding a single column to the user table that holds JSON-encoded data, rather than an extra table. Implementing checks for roles throughout the server code would be most of the work.

WRT OAuth - this would be a nice option, especially integrating to external providers like Google or Apple ID, but username/password will need to be available for systems without continuous internet connectivity.

clydebarrow avatar Aug 25 '21 21:08 clydebarrow

The user table has a permissions column which is a similar idea. Currently there aren't a lot of permissions:

message Permissions {
  bool view_video = 1;
  bool read_camera_configs = 2;

  bool update_signals = 3;
}

but we can expand as needed.

This column is also in the user_session table so a privileged user can create less-privileged sessions, but this isn't really used (yet?).

scottlamb avatar Aug 25 '21 21:08 scottlamb