tacilytics 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -2,9 +2,15 @@
2
2
  # Port (bound to 127.0.0.1; put a reverse proxy / TLS in front)
3
3
  PORT=3920
4
4
 
5
- # Dashboard passphrase (plaintext; keep .env at chmod 600)
5
+ # Dashboard passphrase (plaintext; keep .env at chmod 600).
6
+ # Only used until you create the first account in Settings → Security (then it stops working).
6
7
  WEB_PASSWORD=change-me-to-a-strong-passphrase
7
8
 
9
+ # Public URL of the dashboard. Pins the passkey domain (recommended behind a reverse proxy).
10
+ # TACILYTICS_ORIGIN=https://analytics.example.com
11
+ # Reverse proxies in front (default 1). 0 = no proxy, the socket address is used for rate limits.
12
+ # TRUST_PROXY=1
13
+
8
14
  # Secret for signing session tokens — long random string
9
15
  # node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
10
16
  SESSION_SECRET=change-me
package/README.md CHANGED
@@ -189,6 +189,10 @@ tacilytics sites
189
189
  tacilytics stats example.com 30d
190
190
  tacilytics live example.com --watch
191
191
  tacilytics add example.com
192
+
193
+ # on the server itself (reads the database, no API key)
194
+ tacilytics users
195
+ tacilytics reset-login <username>
192
196
  ```
193
197
 
194
198
  Add `--json` to any command for machine-readable output — perfect for agents and scripts.
@@ -206,13 +210,36 @@ counts unique visitors *per day* and can’t follow the same person across days
206
210
  devices. You get the 95 % that matters (how many, which pages, from where) with 0 %
207
211
  cookie banner — the same approach as Plausible/Fathom.
208
212
 
213
+ ## Team, 2FA & passkeys
214
+
215
+ Out of the box the dashboard opens with one shared passphrase (`WEB_PASSWORD`). When you want more:
216
+
217
+ 1. Sign in, open **Settings → Security** and create your **owner account**. From then on `WEB_PASSWORD` no longer opens anything, so nobody can walk around your 2FA with it.
218
+ 2. **Settings → Team → Invite someone** creates a link that works **once, for 7 days**. Send it yourself; Tacilytics sends no e-mail.
219
+
220
+ | Role | Can |
221
+ |---|---|
222
+ | **Owner** | everything, including the team |
223
+ | **Admin** | add/remove sites, share links, funnels |
224
+ | **Viewer** | read only — optionally limited to **specific sites** |
225
+
226
+ Everyone can turn on, **optionally**:
227
+
228
+ - **Passkeys** (Face ID, Touch ID, Windows Hello, security keys). A passkey replaces password *and* code and cannot be phished.
229
+ - **One-time codes (TOTP)** from any authenticator app, with 10 single-use **recovery codes**. Codes cannot be reused.
230
+
231
+ **Locked out?** On the server: `tacilytics users` lists accounts, `tacilytics reset-login <username>` prints a new password, turns 2FA off and ends that person's sessions. An owner can also reset a teammate's 2FA in Settings → Team.
232
+
233
+ The API key (`API_KEY`, Bearer) keeps owner rights for agents and the CLI.
234
+
209
235
  ## Configuration
210
236
 
211
237
  | Env var | Default | Purpose |
212
238
  |---|---|---|
213
239
  | `PORT` | `3920` | Port (bound to 127.0.0.1) |
214
- | `WEB_PASSWORD` | `tacilytics` | Dashboard passphrase |
215
- | `SESSION_SECRET` | random | Session-token secret |
240
+ | `WEB_PASSWORD` | `tacilytics` | Dashboard passphrase — **only until the first account exists** |
241
+ | `TACILYTICS_ORIGIN` | request host | Public URL of the dashboard, e.g. `https://analytics.example.com`. Pins the passkey domain; recommended behind a proxy |
242
+ | `TRUST_PROXY` | `1` | Number of reverse proxies in front of Tacilytics (e.g. Cloudflare + Caddy = `2`). Rate limits **and visitor hashes** use the address the outermost trusted proxy saw, never the client-written first `X-Forwarded-For` entry. Too low: with two proxies and `1`, every visitor looks like the Cloudflare address and unique visitors collapse. Too high: clients can pick their own address and bypass the limits. Tacilytics binds to 127.0.0.1, so a proxy is always in front and `0` is almost always wrong (everyone becomes 127.0.0.1) |
216
243
  | `INGEST_SALT` | fixed | Extra server-side secret mixed into the visitor hash (on top of the random daily salt) |
217
244
  | `API_KEY` | — | Bearer key for the API / CLI |
218
245
  | `DB_PATH` | `./data/analytics.db` | SQLite path |
@@ -228,7 +255,9 @@ cookie banner — the same approach as Plausible/Fathom.
228
255
  - **Respects DNT / Global Privacy Control** when you add `data-honor-dnt` to the tag.
229
256
  - Engagement time adds **no new identifier** — it’s matched to the visitor’s own daily hash server-side.
230
257
  - Custom event properties are capped (scalars only, size-limited) so they can’t become a PII dumping ground.
231
- - Timing-safe auth; login and the collect endpoint are rate-limited.
258
+ - Passwords hashed with scrypt; session tokens, invite links and recovery codes are stored only as SHA-256. Sessions live in the database (survive restarts, expire after 30 days).
259
+ - Timing-safe auth; login (per IP and per account), the 2FA step and the collect endpoint are rate-limited. Unknown usernames take as long as wrong passwords.
260
+ - Every per-site endpoint checks access: share links see exactly their site, viewers only the sites they were given.
232
261
  - All visitor-supplied values are HTML-escaped before they reach the DOM.
233
262
  - Run behind TLS; keep `.env` at `chmod 600`.
234
263