Repository navigation
feat(accounts): whole-app login, first-run setup gate, multi-user roles, scrypt hashing - #45
Merged
Merged
Conversation
…nstall, and update
Multi-repo duplicate-id handling was incoherent: browse de-duplicated by id
("newest/whatever-wins"), install picked the FIRST repository that had the
id, and update flags scanned every repo. Ordering (repo insertion time) was
an implicit side effect and there was no provenance in install requests.
New, documented contract (docs/architecture.md, "Marketplace identity"):
- Browse returns ONE entry per (repository, plugin) — duplicate ids across
enabled repositories are never merged; every entry lists the other
publishers (`also_in_repos`) and is labelled `installed_elsewhere` when
the id is installed from a different repository.
- install(plugin_id, version, source_repo_id) — provenance is explicit and
recorded on InstalledPlugin.source_repo_id. Without a source, an id
published by >1 enabled repository is REFUSED as ambiguous (candidates
listed); a version/id the chosen repo does not publish is refused.
- update() re-installs from the repository the plugin was INSTALLED from —
never from whichever repo is checked first. latest_versions() is
source-aware so duplicate ids elsewhere never drive update badges.
- Duplicates are detected, not ignored: add/refresh-repository responses
carry conflict warnings; marketplace cards show "Also in: <repo>" inline.
- Config-as-code export now records source_repo_url per plugin; import
re-installs from the same repository URL.
UI: install/update/switch buttons pass source_repo_id; switching an id
installed from another repository is an explicit labelled action; conflict
warnings render as toasts + inline card warnings.
New arch test tests/architecture/test_marketplace_repo_identity.py pins the
contract (per-repo browse, ambiguity refusal, provenance recording, source-
aware update, conflict detection).
…s, remove subsonic plugin
Three related "plugin ecosystem hygiene" items:
1. PLUGIN_API_VERSION 1.0.0 -> 1.0.1 (plugins/__init__.py; fnack/plugin_api
SDK_VERSION mirrors). The security batch (permissions enforced on every
facade, context.http=None without "network", secret-at-rest) changed the
plugin contract behaviorally; manifests declaring ^1.0 remain compatible
(1.0.1 is contained), this is a minor, non-breaking bump that records the
change.
2. Remaining security limitations processed (detect + document):
- NEW load-time static scan (plugins/manager.py _network_capable_imports):
plugins importing network-capable modules (requests/urllib/http.client/
httpx/aiohttp/urllib3/socket/websockets) WITHOUT declaring "network"
now get a prominent warning at load — the direct-import bypass of
context.http is surfaced instead of silent. Warning-only (static scans
can't be perfect); bundled official plugins are held to the rule by
tests/architecture/test_plugin_network_honesty.py (network imports =>
"network" declared).
- AUTHORING.md documents both remaining limitations with concrete
guidance: the network-honesty scan + why it warns, and secret-key loss
(plugin_secret.key must back up with the DB; deleting it revokes all
stored plugin secrets; restore without it => re-enter secrets).
3. fnack.subsonic removed from the fnack ecosystem (core side):
- bundled_plugins/fnack.subsonic deleted (paired with fnack-plugins PR
removing it from the catalog/index/README).
- app.py: header docstring, default_disabled set, blueprint comment no
longer reference it.
- docs/architecture.md: official-plugins table row, server.extension
capability example and the media-scan vs server-API distinction
paragraph rewritten at concept level.
- README: subsonic-server claims removed.
- Tests updated: test_essential_plugins optional list, doc-gate subsonic
test now asserts removal + concept distinction, plugin_boundary no
longer requires a bundled server.extension provider.
… roles, scrypt hashing Accounts (services/accounts.py) — replaces the optional-only auth model: - FIRST-RUN GATE: when no user accounts exist (fresh volume / wiped DB), only /setup (+ /health, /static, /login, /logout, /socket.io) is reachable until the initial ADMIN account is created. Pages redirect to /setup; /api returns 403 with a clear error. - LOGIN: session-cookie auth for every page and /api route. Identity precedence: session account > optional M2M API key (X-API-Key / Bearer, unchanged for machine clients) > enabled auth_provider plugins (reverse-proxy SSO). - CREDENTIALS: werkzeug scrypt hashes (salted, one-way) — never plaintext; constant-time verify. New User model (users table; created by the boot create_all, no manual migration needed). - ROLES: admin (manage accounts) vs user (everything else). Only the first account is admin. Account management UI on Settings (list/create/delete/ promote) + change-own-password; admin-only API under /api/accounts. - HARDENING: cookies HttpOnly + SameSite=Lax (+ FNACK_COOKIE_SECURE=1 behind https); cross-origin state changes from session-authenticated requests are refused (Origin check, CSRF); login session 14 days. - UI: login.html + setup.html; account chip + logout in every page nav; Accounts + change-password cards on Settings. - Docs: README/DEPLOY quick-start first-run steps + M2M key note; docs/architecture.md "Accounts & authentication" section; AUTHORING M2M key wording. NOTE: built on top of the plugin-arch-1.0.1/subsonic-removal branch (PR #44) — merge #44 into main first, then this one. Tests: tests/architecture/test_accounts_first_run.py (setup gate, scrypt storage, login/logout, M2M key, role enforcement, CSRF origin check) — 27/27 architecture tests + smoke pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements accounts (
services/accounts.py) — the app's first real authentication:First-run setup gate
When no user accounts exist (fresh volume, or a wiped/reset config DB), only
/setup,/login,/logout,/health,/static,/socket.ioare reachable. Pages redirect to/setup;/api/*returns403 {"error": "No account is configured…"}. The setup page creates the initial admin account; it reappears whenever no account info can be found.Whole-app lockdown
Every page and
/apiroute requires an identity:X-API-Key/Authorization: Bearer) — machine clients keep working unchangedIndustry-standard credential storage
werkzeug.securityscrypt hashes — salted, one-way, constant-time verify. Never plaintext (test-asserted). NewUsermodel (userstable;db.create_all()creates it at boot on existing DBs too — no manual migration).Multi-user with roles
admin(account management) vsuser(everything else); only the first account is admin. Admin-only/api/accountsCRUD + promote/demote;/api/accounts/me/passwordfor everyone. Self-delete and last-admin-demotion are refused.CSRF defence
Session-authenticated state changes from a cross-origin request are refused via an Origin check (plus SameSite=Lax cookies).
UI
login.html,setup.html, account chip + logout in every page nav, and Settings cards for change-password (everyone) and user management (admin).Docs
README/DEPLOY first-run steps + M2M key note +
FNACK_COOKIE_SECURE;docs/architecture.md"Accounts & authentication"; AUTHORING M2M wording.Files
services/accounts.py(new),models.py(User),app.py(guard/blueprint/session config/context processor),templates/{login,setup}.html(new) + nav chips in all pages,static/app.js,docs/architecture.md,README.md,DEPLOY.md,docs/plugins/AUTHORING.md,tests/architecture/test_accounts_first_run.py(new).Verification