Skip to content

Profiles and sign-in

Off until AUTH_TRUSTED_NETWORKS or AUTH_PROXY_SECRET is set: then everyone using Nessie is someone, and the API turns away anyone who isn't.

  • At home (a request from AUTH_TRUSTED_NETWORKS): each device asks Who's listening? and remembers the answer. A profile can have a PIN (4 to 8 digits); a new PIN signs its other devices out, and so does Settings → Profiles → Sign out your other devices (a lost phone; admins can do it for anyone). The first profile is made here too, on a fresh start, and is an admin. Anyone on these networks can pick a profile without a PIN, admins' included: give admins a PIN if guests share your Wi-Fi.
  • Away, through a sign-in service (Authelia, Authentik, oauth2-proxy… behind Caddy, Traefik, nginx): the proxy sends the signed-in user, and a shared secret so Nessie knows the header came from it. Nessie trusts the secret, not the address: Docker and Tailscale can hide where a request came from (through Tailscale, Docker shows its own gateway, e.g. 172.28.0.1). The user is the profile with their username, made the first time they come; their groups decide whether they're an admin (AUTH_ADMIN_GROUP), may make AI playlists (AUTH_AI_GROUP) or may request music from Lidarr (AUTH_REQUEST_GROUP).
  • Anywhere else: turned away.

What each may do: admins change Settings → Server, add and scan libraries, change pictures and details, and use Lidarr; making AI playlists needs admin or the AI permission (they cost money); everyone listens, searches, makes playlists and mixes, and imports playlists. Settings → Profiles adds and changes profiles (admins); anyone changes their own name and PIN, and signs their other devices out. The same person is the same profile at home and away: give their profile their sign-in username.

My libraries: each profile can mark the libraries that are its own (Settings → Libraries, One of mine). It's a preference, not a restriction: the sidebar shows those first and folds the others into Other libraries (one line each, opened in full while you're in one), and the lists that span libraries (Your music: Most played, Most played lately) use just them. None marked: all are. Those lists show in the sidebar only when they span two or more libraries: over one, they'd be that library's own Most played lists, which it shows already.

Listening history: a play counts for whoever played it: in the browser, the person using it; on the server's speakers, whoever last started or queued music there. Most played, Most played lately, the mixes (which favour your favourites), the play rules of custom mixes and AI playlists ("not played in 14 days"), and the Songs sorts by plays and last played are each person's own. An artist's Popular songs and the classic UI (/v2/) count the whole household. Deleting a profile keeps its plays in the household's counts. Plays from before profiles were switched on are no one's: give them to someone with python -m app.cli give-plays USERNAME (in Docker, docker exec <container> python -m app.cli give-plays USERNAME). Liked songs are each person's too; ones liked with profiles off are the household's, and give-likes USERNAME gives them to someone the same way.

Whose playlists and mixes are: a playlist you make (or import, copy or save from the queue or an AI playlist) is yours, and so is a custom mix you save. The rest of this goes for both. Yours are shared: the household sees and plays them, and can Save a copy to change, but only you change the original. Make one private (its ⋯ menu) and only you see it, admins included. The household's playlists (ones from before profiles, and any given to it) are everyone's to change. Admins look after the household's playlists but not its taste: they can delete or give away anyone's they can see, not change them. The Playlists page shows all of them, yours, or the ones shared with you; the Mixes page has Your mixes, then Shared mixes. A shared mix still picks for whoever plays it: "not played in 2 weeks" means your plays when you play it. Deleting a profile asks what happens to its playlists and mixes: the household's, someone else's, or deleted. Those from before profiles are the household's; give some to their owner with python -m app.cli give-playlists USERNAME and give-mixes USERNAME (each lists them to choose from by number; name them, or --all, to skip the list). A playlist whose name they have already becomes "Name (household)".

With Authelia and Caddy (in your Caddyfile; the secret also in Nessie's AUTH_PROXY_SECRET):

audio.example.com {
    encode
    route {
        # Only the sign-in service says who's signed in, never the browser
        request_header -Remote-User
        request_header -Remote-Groups
        request_header -Remote-Name
        request_header -Remote-Email
        forward_auth authelia:9091 {
            uri /api/authz/forward-auth
            copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
        }
        reverse_proxy nessie-server:8000 {   # where the proxy reaches Nessie (e.g. its Tailscale name)
            header_up X-Nessie-Proxy-Secret {$NESSIE_PROXY_SECRET}
        }
    }
}

and in Authelia, groups nessie-admins, nessie-ai and nessie-requests for whoever should have them. route keeps the steps in that order. Authelia replaces those headers for someone signed in; removing them first also covers a request it lets through without saying who it is (a bypass rule).

Keep the secret secret: with it, a request can say it's anyone, an admin included. Two more things help:

  • Only the proxy should reach Nessie's port. Nessie can't check that itself when Docker hides where requests come from. Through Tailscale, an ACL does it: tag the two machines and allow only the proxy's to the port ({"src": ["tag:vps"], "dst": ["tag:nessie"], "ip": ["tcp:8000"]} in the policy's grants, with no other rule reaching tag:nessie on 8000). Or a firewall rule on Nessie's machine, in Docker's DOCKER-USER chain.
  • Test it: signed in, curl -b "<your sign-in cookie>" -H "Remote-User: someone-else" https://audio.example.com/v1/me should answer as you.

Other sign-in services name their headers differently:

User Name Groups (separator)
Authelia (default) Remote-User Remote-Name Remote-Groups (,)
Authentik X-authentik-username X-authentik-name X-authentik-groups (\|)
oauth2-proxy X-Forwarded-User X-Forwarded-Preferred-Username X-Forwarded-Groups (,)
Tailscale Serve Tailscale-User-Login Tailscale-User-Name (none: set admins in Nessie)