Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs-developers/frontend/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ src/components/ ui/ (design-system primitives - Text, Icon, Button, Card, Sh
src/stores/ Zustand: session (connections + tokens), settings, search
src/i18n/ i18next init, LanguageProvider, locale catalogs (locales/*.json)
src/theme/ ThemeProvider + raw color tokens (tokens.ts)
src/lib/ storage, secure-store, paths, format, pairing, recovery, device,
src/lib/ storage, secure-store, paths, format, pairing, known-servers, device,
base-url, layout (the one phone->desktop breakpoint),
register-sw, and other pure helpers
modules/audiosilo-player/ the local Expo module (Swift + Kotlin + TS bridge)
Expand Down Expand Up @@ -75,7 +75,7 @@ handful of standalone screens.
|---|---|---|
| - (root layout) | `src/app/_layout.tsx` | Mounts the provider tree (`GestureHandlerRootView` → `SafeAreaProvider` → `LanguageProvider` → `ThemeProvider` → `ApiProvider`), hydrates the session/settings/downloads stores, imports `@/lib/register-sw` for its side effect, mounts the headless `BookEndedListener` (drives the end-of-book flow, see [Playback](playback.md#ending-a-book-end-credits-and-up-next)), and runs `useAppResume` (foreground refresh + the Android swipe-from-recents reset). Declares the `(app)` stack and the `player`/`finished` screens as `fullScreenModal`s. |
| - (web HTML shell) | `src/app/+html.tsx` | The static HTML wrapper for every exported web route: PWA manifest/favicon links (base-prefixed) and a dark backdrop painted before React mounts so there is no white flash. |
| `(app)` guard | `src/app/(app)/_layout.tsx` | The auth gate: `loading` → spinner, `unauthenticated` → `<Redirect href="/connect" />`, otherwise wraps children in `AppShell` (header + nav). Also backfills `has_password`/`has_recovery` on sessions persisted before those flags existed. |
| `(app)` guard | `src/app/(app)/_layout.tsx` | The auth gate: `loading` → spinner, `unauthenticated` → `<Redirect href="/connect" />`, otherwise wraps children in `AppShell` (header + nav, which renders the `ReconnectBanner` when a connection's token is rejected - see below). Also backfills `has_password`/`has_recovery` on sessions persisted before those flags existed. |
| `/` | `(app)/index.tsx` | Home: continue-listening cards, recently-added shelf, favourites - aggregated **across every connected server** via the `use*All` hooks. |
| `/browse?type=recent\|finished` | `(app)/browse.tsx` | The "see all" grid behind a home shelf. |
| `/search` | `(app)/search.tsx` | Search across all connections, de-duplicated; shares its query text with the desktop top bar via `useSearchStore`. |
Expand All @@ -85,13 +85,13 @@ handful of standalone screens.
| `/library/[libraryId]/[...path]` | `(app)/library/[libraryId]/[...path].tsx` | Nested folder browse - same `BrowseScreen`, the catch-all segments become the folder `path` (helpers in `src/lib/paths.ts`). |
| `/book/[libraryId]/[...path]` | `(app)/book/[libraryId]/[...path].tsx` | Book detail: play/resume, download control, chapters, bookmarks, notes, listening history, other versions of the same book. |
| `/downloads` | `(app)/downloads.tsx` | Downloaded books + storage used ([Offline](offline.md)). |
| `/settings` | `(app)/settings.tsx` | Playback tunables, language, theme, connections, self-service password/recovery, per-server API keys (capability-gated), sign-out. |
| `/settings` | `(app)/settings.tsx` | Playback tunables, language, theme, connections, self-service password (the sign-out guard nudges a password-less user to set one via `sign-out-confirm.tsx`), per-server API keys (capability-gated), sign-out. |
| `/player` | `src/app/player.tsx` | The full player, presented as a full-screen modal above the shell. Accepts `libraryId`/`path` (+ optional `position`/`track`) params and gates playback start on the chapters query settling. |
| `/finished` | `src/app/finished.tsx` | The end-credits screen shown when a book finishes (or from the player's menu). A root modal sibling of the player; renders `EndCredits` with an "up next" suggestion. See [Playback](playback.md#ending-a-book-end-credits-and-up-next). |
| `/connect` layout | `src/app/connect/_layout.tsx` | Onboarding stack. An **authenticated** user is bounced home unless they are adding another server (`?add=1`, a pairing `?token=`, or a sign-in mid-flow via `pendingServerUrl`) - the app supports multiple simultaneous server connections. |
| `/connect` | `connect/index.tsx` | Enter a server URL (or auto-redeem a pairing token arriving via deep link / QR `web_url`). |
| `/connect` | `connect/index.tsx` | Enter a server URL (or auto-redeem a pairing token arriving via deep link / QR `web_url`). When signed out of everything, also lists previously-connected servers as one-tap **Reconnect** shortcuts (`src/lib/known-servers.ts`), each pre-filling the address so the user only re-enters a code or password. |
| `/connect/scan` | `connect/scan.tsx` | Camera QR scanner (`expo-camera`) for the pairing QR. |
| `/connect/sign-in` | `connect/sign-in.tsx` | Auth-code **or** username/password sign-in against the pending server. The code field accepts both invites and recovery codes - they redeem through the same path. |
| `/connect/sign-in` | `connect/sign-in.tsx` | Auth-code **or** username/password sign-in against the pending server. Reached fresh, from a **Reconnect** shortcut, or from the dead-token `ReconnectBanner` (`src/components/layout/reconnect-banner.tsx`), all with the address pre-filled via `pendingServerUrl`. The code field redeems invite codes (legacy recovery codes still redeem server-side, but the app no longer mints them). |
| `/demo` | `src/app/demo.tsx` | Public demo landing: mints a throwaway session on a demo-mode server and shows the pairing QR so the same demo user opens on a phone. |

## Styling conventions
Expand Down
4 changes: 2 additions & 2 deletions docs-developers/frontend/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ Co-located suites exist for:
| Downloads | `src/downloads/store.test.ts` |
| Stores | `src/stores/session.test.ts`, `settings.test.ts` |
| i18n | `src/i18n/language.test.ts`, `language-provider.test.tsx` |
| Account flows | `src/components/account/use-recovery-code.test.tsx`, `use-sign-out.test.tsx` |
| Account flows | `src/components/account/use-api-keys-manager.test.tsx`, `use-sign-out.test.tsx` |
| UI data | `src/components/ui/icon-data.test.ts` (validates every vendored SVG glyph) |
| `src/lib` helpers | `alpha-sections`, `app-resume`, `base-url`, `dedup`, `format`, `nav`, `pairing`, `paths`, `progress-view`, `recovery`, `scroll-memory`, `secure-store`, `share`, `support` |
| `src/lib` helpers | `alpha-sections`, `app-resume`, `auth-failure`, `base-url`, `dedup`, `format`, `known-servers`, `nav`, `pairing`, `paths`, `progress-view`, `scroll-memory`, `secure-store`, `share`, `support` |

Not covered by unit tests, by design or necessity: `src/app/**` screens (kept
logic-free), and the **native module** (`modules/audiosilo-player`) - Swift and
Expand Down
16 changes: 12 additions & 4 deletions docs-developers/server/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ Response: `204 No Content`.

### `POST /api/v1/auth/recovery`

:::caution Legacy - being retired
The frontend player no longer offers recovery codes; it nudges users to set a
password and relies on the reconnect flow instead. This endpoint (and the two
recovery revoke routes below) is kept only so older app builds keep working, and
is slated for removal once those clients age out. New clients should not call it.
:::

*Session.* Mints (or replaces) the caller's durable **recovery code** - an auth
code with unlimited uses and no expiry, owned by the user, redeemable through
the normal `/auth/redeem` flow. Returned exactly once; only its hash is stored.
Expand All @@ -263,8 +270,8 @@ Response `201`:

### `DELETE /api/v1/auth/recovery`

*Session.* Removes the caller's recovery code (no-op if none). Response:
`204 No Content`.
*Legacy (see above).* *Session.* Removes the caller's recovery code (no-op if
none). Response: `204 No Content`.

## Personal API keys

Expand Down Expand Up @@ -1010,8 +1017,9 @@ logs; the connect page auto-redeems it client-side.

### `DELETE /api/v1/admin/users/{id}/recovery`

Revokes a user's recovery code (the admin's only lever for a leaked one, since
recovery codes are not listable). No-op if none. `204 No Content`.
*Legacy - being retired alongside `POST /auth/recovery` once old clients age
out.* Revokes a user's recovery code (the admin's only lever for a leaked one,
since recovery codes are not listable). No-op if none. `204 No Content`.

### `POST /api/v1/admin/authcodes/{id}/rotate`

Expand Down
9 changes: 9 additions & 0 deletions docs-developers/server/auth-and-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ password-less user who saved a recovery code can re-pair on any device via the
normal connect screen, with no admin involved. `GenerateRecoveryCode`
atomically replaces any existing recovery code for the user.

:::note Client UI retired
The frontend player no longer mints or reads recovery codes - it nudges users to
set a password and uses the reconnect flow (see
[Frontend overview](../frontend/overview.md)) instead. The server keeps `POST
/auth/recovery` and the admin revoke route so older app builds still redeem
codes they minted earlier; both are slated for removal once those clients age
out.
:::

## Demo-account restrictions

Public demo mode (`config.demo.*`) creates throwaway `is_demo` accounts.
Expand Down
18 changes: 2 additions & 16 deletions docs-users/admin/users-and-invites.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Users and invites"
description: "Creating AudioSilo accounts, getting people connected with invite links, recovery codes, and the safety rails around admin accounts."
description: "Creating AudioSilo accounts, getting people connected with invite links, and the safety rails around admin accounts."
---

The **Users** section of the [admin console](console-tour.md) is where you
Expand Down Expand Up @@ -100,19 +100,6 @@ downgrades it to the defaults).

To kill an invite outright, click **Revoke**.

## Recovery codes

A **recovery code** is a listener's own durable way back in: a code they mint
for themselves from the player's settings (see
[Your account](../listening/account.md)), which never expires and has no use
limit. It means someone with no password can sign out - or lose their phone -
and reconnect without needing you to mint a new invite.

In the drawer, the **Recovery code** row only tells you whether one is *set* or
*none*. As the admin you can **Revoke** a recovery code (for example if it
leaked), but you can never view it - recovery codes belong to the user, and the
server stores only a fingerprint.

## The user detail drawer

![The user detail drawer](/img/screenshots/admin/user-detail.png)
Expand All @@ -122,8 +109,7 @@ Clicking a user opens a drawer with four blocks:
- **Account** - the **Role** dropdown; **Status** with an **Enable/Disable**
button; **Password** with a **Set password**/**Change** control (for a
listener, saving an empty password clears it, returning the account to
invite-only pairing); the **Recovery code** status with its **Revoke**
button; and **Last active**.
invite-only pairing); and **Last active**.
- **Access** - the shares granted to this user, each with a **Revoke** button,
plus a **Grant access** control: pick **Whole library** or **Share**, choose
the target, and click **Grant**. (For an admin this block just notes they
Expand Down
8 changes: 2 additions & 6 deletions docs-users/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,8 @@ Demo accounts are deliberately disposable:

## What demo accounts can't do

To keep throwaway accounts throwaway, two things are switched off for them:

- **Setting a password** - refused for demo accounts.
- **Minting a recovery code** - likewise refused.

Those are the two ways a normal account makes itself durable
To keep throwaway accounts throwaway, **setting a password is switched off** for
them. That's how a normal account makes itself durable
([Your account](listening/account.md)), so a demo session can never turn itself
into a permanent login. Sign out (or go idle long enough) and it's gone.

Expand Down
23 changes: 8 additions & 15 deletions docs-users/listening/account.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Your account and settings
description: "The Settings screen: appearance, language, playback preferences, passwords, recovery codes, pairing extra devices, and signing out safely."
description: "The Settings screen: appearance, language, playback preferences, setting a password, pairing extra devices, and signing out safely."
---

The **Settings** tab is where you tune the player and look after your account:
Expand Down Expand Up @@ -55,34 +55,27 @@ Shows who you're signed in as, your role, and the server address - plus the two

### Set a password

Accounts created by invite often start **without** a password - you signed in with a code, and that's fine day to day. Setting one gives you a normal username + password sign-in:
Accounts created by invite often start **without** a password - you signed in with a code, and that's fine day to day. Setting a password is your **reliable way back in** on any device:

- Tap **Set a password** (or **Change password**), enter a new password of at least 8 characters, and save.
- Changing an existing password asks for your **current password** first.

### Recovery code - your way back in

A **recovery code** is a code only you hold that signs you back in on any device - *"A code you keep to sign back in yourself - no admin needed."*

Why you want one: if you sign out (or get a new phone) and have **no password and no recovery code**, the only way back into your account is asking your admin for a fresh invite. A recovery code removes that dependency entirely - it never expires and works any number of times, typed into the same code box as an invite (see [Connecting](connecting.md)).

- Tap **Generate recovery code**. The code is shown **once** - *save it somewhere safe* (a password manager is ideal). It won't be shown again.
- **Regenerate recovery code** replaces it; the app warns you first, because the old code stops working the moment a new one exists.

:::warning
Treat a recovery code like a password: anyone who has it can sign in as you. If yours ever leaks, regenerate it (or ask your admin to revoke it).
:::
Why it's worth doing, especially if you were invited by pairing and never set one: if you ever sign out or get a new phone, a username and password sign you straight back in with no help from anyone. Without one, getting back in means asking your admin for a fresh invite - so setting a password once is the safety net that keeps your account in your own hands.

### API keys

If your server supports them, an **API keys** section here lets you create keys for dashboards, scripts, and other tools that reach your server on your behalf. See [API keys for integrations](api-keys.md).

### Signing out

**Sign out** disconnects this device. If you're about to sign out with **neither a password nor a recovery code**, the app stops you with a warning - *"Without one you'll need a new invite from your admin to sign back in"* - and offers to **Set a recovery code** right there. Take the offer; it's the whole reason the button exists.
**Sign out** disconnects this device. If you're about to sign out **without a password set**, the app stops you with a warning - *"Without one you'll need a new invite from your admin to sign back in on this server"* - and offers to **Set a password** right there. Take the offer; it's the whole reason the button exists.

Signing out also **removes this server from the app**, so it deletes that server's downloaded books from the device and any progress that hasn't synced yet. The app warns you when there are downloads to lose. (Books, progress, and bookmarks stored on the server are safe - sign back in and they're all there.)

:::tip
Signing out isn't a big deal. Next time you open the connect screen, any server you've connected to before shows a one-tap **"Reconnect to &lt;your server&gt;"** shortcut with the address already filled in - so you only re-enter your code or password, never the server address. See [Connecting and signing in](connecting.md#getting-back-in-after-signing-out).
:::

## Devices

Pair another phone, tablet, or browser to your account without a new invite: tap **Add a device** and a QR code appears. Scan it with the other device (or share the link to it) and it signs straight in. The details are in [Connecting and signing in](connecting.md).
Expand Down
4 changes: 2 additions & 2 deletions docs-users/listening/api-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ description: "Create a personal API key so a dashboard, script, or monitoring to

An **API key** is a long, secret token you create for yourself so another app can talk to your AudioSilo server *as you*, without handing over your password. It is the right tool when you want a home dashboard (such as Heimdall), a script, a cron job, or an uptime monitor to reach your server automatically.

Each key acts as you for everyday requests - browsing your libraries, reading your books, saving your listening progress. You can create as many as you like, name each one, and revoke any of them at any time. (The one thing a key can't do is manage your account itself - it can't create another key, set your password, or generate a recovery code. Those always need you signed in normally, so a leaked key can't lock you out or quietly multiply itself.)
Each key acts as you for everyday requests - browsing your libraries, reading your books, saving your listening progress. You can create as many as you like, name each one, and revoke any of them at any time. (The one thing a key can't do is manage your account itself - it can't create another key or set your password. Those always need you signed in normally, so a leaked key can't lock you out or quietly multiply itself.)

## Where to find it

In the player, open **Settings** and go to your server's account area - the same place you set a password or a recovery code (see [Your account and settings](account.md)). There you'll find an **API keys** section.
In the player, open **Settings** and go to your server's account area - the same place you set a password (see [Your account and settings](account.md)). There you'll find an **API keys** section.

:::note
The section only appears on servers new enough to support API keys, and it is hidden for **demo accounts** - a throwaway demo login can't create keys.
Expand Down
Loading
Loading