store the token provisioning mints, so members can open video
Provisioning already created each member's Jellyfin account and signed in as them to get a token — its own header says the token exists so "the person's Officer session can already read their library". It was then dropped, because linkJellyfinAccount had nowhere to put it. So every non-owner resolved to no config and got a 503 setup form, asking them to connect a Jellyfin server with a password nobody knows. It reads as a permissions fault and is not one: the grant, the role and the account list were all correct. jellyfin_accounts now carries access_token and device_id, getConfig falls back to the link row when the caller has no registry of their own, and there is no path from there to the owner's token — a member resolving to the owner's credential would read the owner's watch state and every library. POST /_config/:id/provision repairs accounts linked before the column existed. It resets the password to mint, which is free on an account Officer created (random, discarded, known to nobody) and destructive on an adopted one, so adopted accounts are skipped unless includeAdopted says otherwise. Applied on alpha: conceiba and guipinto minted and verified against the live server; fedra and diogo skipped as adopted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
-4
@@ -19,7 +19,7 @@
|
||||
// 3. Almost every per-user route needs the user id in the PATH (`/Users/{id}/Views`), and it is not
|
||||
// derivable from the token without a round trip — which is why it is pinned on the row at sign-in.
|
||||
|
||||
import { getActiveJellyfinCredentials } from '../db';
|
||||
import { getActiveJellyfinCredentials, getLinkedJellyfinCredentials } from '../db';
|
||||
|
||||
/** Everything needed to make one call. A candidate being validated has this and nothing else yet. */
|
||||
export type UpstreamTarget = {
|
||||
@@ -49,14 +49,26 @@ const TTL_MS = 60_000;
|
||||
const cache = new Map<number, { cfg: UpstreamConfig | null; at: number }>();
|
||||
|
||||
/**
|
||||
* The owner's SELECTED server, or null when /jellyfin has none yet — the sidecar then answers 503, and the
|
||||
* UI turns that into the setup form rather than a screen of empty shelves.
|
||||
* What THIS account reads Jellyfin with, or null when there is nothing — the sidecar then answers 503, and
|
||||
* the UI turns that into the setup form rather than a screen of empty shelves.
|
||||
*
|
||||
* Two sources, in this order:
|
||||
*
|
||||
* 1. **A server this account registered itself.** The owner's normal case, and also a member who
|
||||
* deliberately connected a Jellyfin of their own. Checked first so that a person who went to the
|
||||
* trouble of adding one is not overridden by an inherited link.
|
||||
* 2. **A provisioned link on the owner's active server**, carrying that person's OWN token. This is how
|
||||
* a member reaches the library without ever holding a credential or seeing a setup form.
|
||||
*
|
||||
* The second source is what makes `userProvisioning` mean anything. Before 2026-08-21 it did not exist,
|
||||
* so every member fell through to null and got a 503 telling them to connect a Jellyfin server — advice
|
||||
* they could not act on, since the account Officer made for them has a password nobody knows.
|
||||
*/
|
||||
export async function getConfig(userId: number): Promise<UpstreamConfig | null> {
|
||||
const hit = cache.get(userId);
|
||||
if (hit && Date.now() - hit.at < TTL_MS) return hit.cfg;
|
||||
|
||||
const creds = await getActiveJellyfinCredentials(userId);
|
||||
const creds = (await getActiveJellyfinCredentials(userId)) ?? (await getLinkedJellyfinCredentials(userId));
|
||||
const cfg = creds
|
||||
? {
|
||||
id: creds.id,
|
||||
|
||||
Reference in New Issue
Block a user