Repository navigation
v3.7.5 — Passkeys, and a log of who did what
Two ways of proving who you are, and one record of what everybody did. All
three are optional, and the log is off until somebody asks for it: on a machine
one person uses, a record of what that person did all day has no reader.
Sign in with a passkey
Any local account can add one in Settings → Passkeys and sign in with a
fingerprint, a face or the device's PIN instead of a password.
- It cannot be phished, watched or replayed. The key never leaves the
device, and what it signs names this site: a copy of the sign-in page on
another address gets nothing it can use. - A passkey that was unlocked to be used answers the second factor as well —
the device, plus whoever can open it. One used without that unlock still asks
for the code. - The last door is kept. An account with no password cannot remove its last
passkey. An administrator can remove all of them from an account, the way they
can already reset its second factor. - No dependency was added for it. The one serious library exists mostly to
verify which brand of authenticator is being held, which is the single thing
this deliberately does not want to know; the two ceremonies and a CBOR reader
are written here, and the tests drive them with a software authenticator that
encodes CBOR its own way, so the reader can disagree with the writer. - Browsers only allow this on a secure page served from a hostname, so it is
not offered over plain HTTP or on an IP address — the settings page says which
of the two is in the way instead of showing a button that cannot work. - Passkeys belong to local accounts. Where the identity provider owns the
sign-in (AUTH_MODE=oidc), they are not offered.
Documentation: Admin & Access
An activity log, off unless somebody asks for it
Settings → Activity log turns it on, and nothing from before that moment is
in it. Seventeen kinds of event: who signed in — including who tried and failed,
and under what name — what was downloaded, uploaded, sent to the trash, restored
or removed for good, what left through which share link and what arrived through
one, every change to a password, a second factor or a passkey, and every account
or setting an administrator changed.
- Administrators read it, filtered by kind, by account, by outcome or by
words, and paged from the moment of the last line rather than by an offset, so
lines arriving while somebody reads do not shift the page. - Emptying it leaves the line that says who emptied it, and how many lines
went. It is written after the deletion, which is what makes it the only one to
survive. - Off costs nothing measurable — 0.017 ms per event that is not written —
and the retention is swept hourly whether the log is on or off, so switching it
off hands the disk back rather than freezing yesterday's rows. - Nothing here can fail the request it describes. A download does not stop
because a line could not be written. - No link to the accounts table. Removing an account takes its files and its
sessions with it; what it did while it existed is precisely what a log is for,
so the name is copied into the line. - Which kinds are recorded is not a setting, and that is a decision rather
than an omission. Every comparable project — FileBrowser Quantum, Nextcloud's
audit app, Seafile Pro, FileRun, Filestash, Microsoft Purview — offers one
switch, a retention and filters at the reading end; the value of an audit
trail is that it is complete. The reasoning and what was checked are in
TODO.md.
The address in a line is the person's, not the proxy's
Every line used to carry whoever opened the socket, which in a container is the
Docker bridge as often as it is anybody: 172.18.0.1 line after line, and
nothing anywhere to say why.
- One helper now decides the address for everything that writes one down:
the log, the share counters, the refused-passkey warning. CF-Connecting-IPis read first — what Cloudflare sets in front of a
tunnel on a public hostname — thenX-Forwarded-For, thenX-Real-IP.
Nginx's own example configuration sends that last one and not the first, which
left those installations naming the proxy on every line.- Only from a proxy
TRUST_PROXYsays may be believed. A log that the
people in it can write is worse than one that names the proxy. ::ffff:192.168.1.7is written the way somebody reading the log would write
it.- A proxy being ignored is now said once in the server's own log, naming the
address it announced and the one being recorded instead. And
GET /api/activity/address(administrators) settles the whole question from a
browser: what would be recorded, the machine at the other end of the socket,
whether it is believed, the rule in force — which is also how to see that
TRUST_PROXYnever reached the process — and every forwarding header that
arrived. - The limit worth knowing: a tunnel that forwards raw TCP rather than HTTP, such
as a Cloudflare private network route over WARP, has no HTTP for a header to
be added to. Nothing recovers an address that never arrived.
Documentation: Reverse Proxy & Networking
A folder's address reads like a path again
/browse/Stacks/data, not /browse/Stacks%2Fdata. A router parameter is one
segment, so every slash inside it was encoded; each segment is now encoded on
its own, the way the editor's addresses always were. Addresses already saved keep
working. It is not only about looks: Apache refuses an encoded slash unless
AllowEncodedSlashes is turned on, so this was a 404 waiting behind somebody's
reverse proxy.
Smaller
- Each kind of event is named in the reader's own language, in all fifteen.
- A deletion names the file rather than the folder it was in, and a restore names
what came back. - In French the switch read “Enregistrer l'activité” directly above a button
reading “Enregistrer”: it is “Consigner l'activité” now, and the same
collision was fixed in Polish. - Four kinds of event were offered by the filter and written by nothing at all,
and a deletion made from the interface — which goes through a different route
than the API's — wrote nothing at all. Both are fixed, and a test now fails if
a kind exists in the list and nowhere else. - The check that every code change carries a test reads the
No-test:line
itself rather than asking git for a trailer it cannot parse when the line
wraps, and it audits the whole range even when an earlier run was cancelled. - This changelog gains the v3.7.0 entry it never got.
Upgrading
Schema 23, two new tables (passkeys, activity_events), applied at the first
start. Nothing to do by hand, and nothing is recorded until the log is switched
on.
ghcr.io/cerede2000/explorer:3.7.5
ghcr.io/cerede2000/explorer:3.7.5-lean
Both are on Docker Hub under the same tags, for linux/amd64 and linux/arm64.