ARCHITECTURE.md: - Node version: 18+ → 22 (Alpine) - Tech stack: add helmet, express-rate-limit, cookie-parser, testing tools - Directory structure: add server/app.js, verifyCsrf.js, tokenStore.js, sanitizeError.js, tests/, docs/, .gitea/workflows/, vitest.config.js - §4.1: document app.js factory (createApp) vs index.js entry point; CSP nonce, rate limiters, CSRF middleware, trust proxy - §4.2: add CSRF Required column; document verifyCsrf; fix auth note - §4.3: add tokenStore.js and sanitizeError.js descriptions - §6 Auth flow: add rememberMe, rate limiter, stable DeviceId, server-side token store, CSRF token issuance, correct cookie TTL (session/30d not 24h) - §9 API: add csrfToken to login response, rememberMe field, 400/429 codes; add GET /api/auth/csrf endpoint; fix /me response; fix /logout CSRF note - §11 Config: add DATA_DIR, COOKIE_SECRET, TRUST_PROXY, NODE_ENV; split into Core / Emby / Service Instances / Tuning sections - §12 Deployment: update Dockerfile description to multi-stage node:22-alpine; add COOKIE_SECRET, TRUST_PROXY, named volume to compose example; add security hardening checklist; add CI/CD table diagrams/seq-auth.puml: - Add TokenStore participant - Add rememberMe, CSRF token issuance, stable DeviceId note - Add login rate limiter note - Add GET /csrf refresh flow - Add server-side token revocation on logout diagrams/class-server.puml: - Add app.js createApp() factory class - Add verifyCsrf middleware class - Add TokenStore and SanitizeError utility classes - Update auth.js routes (add GET /csrf) - Fix relationships: entry → appfn → routes diagrams/component.puml: - Add app.js factory component - Add helmet, express-rate-limit components - Add verifyCsrf middleware component - Add tokenStore.js and sanitizeError.js utility components - Fix wiring: entry → createApp() → mounts routes Dockerfile: - Fix stale comments referencing better-sqlite3 and SQLite server/routes/auth.js: - Fix stale comment: SQLite-backed → JSON file-backed
116 lines
3.0 KiB
Plaintext
116 lines
3.0 KiB
Plaintext
@startuml component
|
|
!theme plain
|
|
title sofarr — Component Diagram
|
|
|
|
skinparam componentStyle rectangle
|
|
skinparam packageStyle frame
|
|
|
|
package "Browser" as browser {
|
|
[index.html] as html
|
|
[app.js] as appjs
|
|
[style.css] as css
|
|
html ..> appjs : loads
|
|
html ..> css : loads
|
|
}
|
|
|
|
package "Express Server" as server {
|
|
|
|
[index.js\nEntry Point] as entry
|
|
[app.js\ncreatApp() factory] as appfactory
|
|
|
|
package "Middleware" {
|
|
[helmet\n(CSP nonce, HSTS)] as hm
|
|
[express-rate-limit\n(API + login)] as rl
|
|
[cookie-parser\n(signed cookies)] as cp
|
|
[express.json\n(64kb limit)] as ej
|
|
[express.static] as es
|
|
[requireAuth.js] as requireauth
|
|
[verifyCsrf.js\n(double-submit)] as verifycsrf
|
|
}
|
|
|
|
package "Routes" as routes {
|
|
[auth.js\n/api/auth\n(pre-CSRF)] as auth
|
|
[dashboard.js\n/api/dashboard] as dashboard
|
|
[emby.js\n/api/emby] as emby_route
|
|
[sabnzbd.js\n/api/sabnzbd] as sab_route
|
|
[sonarr.js\n/api/sonarr] as sonarr_route
|
|
[radarr.js\n/api/radarr] as radarr_route
|
|
}
|
|
|
|
package "Utilities" as utils {
|
|
[poller.js] as poller
|
|
[cache.js\nMemoryCache] as cache
|
|
[config.js] as config
|
|
[qbittorrent.js\nQBittorrentClient] as qbt
|
|
[tokenStore.js\n(tokens.json)] as tokenstore
|
|
[sanitizeError.js] as sanitize
|
|
[logger.js] as logger
|
|
}
|
|
|
|
entry --> appfactory : createApp()
|
|
entry --> es : serve public/
|
|
entry --> poller : startPoller()
|
|
|
|
appfactory --> hm
|
|
appfactory --> rl
|
|
appfactory --> cp
|
|
appfactory --> ej
|
|
appfactory --> auth : mount before verifyCsrf
|
|
appfactory --> verifycsrf : applied to all /api below
|
|
appfactory --> dashboard
|
|
appfactory --> emby_route
|
|
appfactory --> sab_route
|
|
appfactory --> sonarr_route
|
|
appfactory --> radarr_route
|
|
|
|
emby_route --> requireauth
|
|
sab_route --> requireauth
|
|
sonarr_route --> requireauth
|
|
radarr_route --> requireauth
|
|
dashboard --> requireauth
|
|
|
|
auth --> tokenstore : storeToken / getToken / clearToken
|
|
|
|
dashboard --> cache : read poll:* keys
|
|
dashboard --> poller : pollAllServices()\n(on-demand mode)
|
|
dashboard --> config : getSonarrInstances()\ngetRadarrInstances()
|
|
dashboard --> qbt : mapTorrentToDownload()
|
|
|
|
poller --> cache : set poll:* keys
|
|
poller --> config : get all instances
|
|
poller --> qbt : getTorrents()
|
|
poller --> logger
|
|
|
|
qbt --> config : getQbittorrentInstances()
|
|
qbt --> logger
|
|
|
|
auth ..> sanitize
|
|
dashboard ..> sanitize
|
|
}
|
|
|
|
cloud "External Services" as external {
|
|
[Emby / Jellyfin] as emby
|
|
[SABnzbd] as sab
|
|
[Sonarr] as sonarr
|
|
[Radarr] as radarr
|
|
[qBittorrent] as qbit
|
|
}
|
|
|
|
auth --> emby : authenticate\nuser profile
|
|
dashboard --> emby : GET /Users\n(user-summary + tag badge classification)
|
|
emby_route --> emby
|
|
sab_route --> sab
|
|
sonarr_route --> sonarr
|
|
radarr_route --> radarr
|
|
|
|
poller --> sab : queue + history
|
|
poller --> sonarr : tags + queue + history
|
|
poller --> radarr : tags + queue + history
|
|
qbt --> qbit : login + torrents/info
|
|
|
|
appjs --> auth : POST /login\nGET /me
|
|
appjs --> dashboard : GET /user-downloads\nGET /status
|
|
es --> html : serve static
|
|
|
|
@enduml
|