Commit Graph
259 Commits
Author SHA1 Message Date
ancsemi 22d5505ccc Let a role's upload cap raise the admin's too 2026-10-17 00:54:19 -04:00
ancsemi 212d592b56 Send silent messages with @silent: no sound, pop-up or push 2026-10-16 12:19:12 -04:00
ancsemi 507f1009bb Mark unseen thread replies and sound only for threads you take part in
A thread reply reached everyone in the channel. Now the server names the
people taking part (starter and repliers, minus muted forum topics); only
they hear a reply, plus mentions and replies to you. The thread chip shows
a dot and N new, the Threads button counts threads with news, and the
sidebar dots a channel with news in a thread you follow. Opening clears it.
2026-10-16 11:36:59 -04:00
ancsemi 1b2421e906 Add a checked upload route for badge icons (#5632) 2026-10-16 09:36:35 -04:00
ancsemi cbee30034f Suggest a high port or reverse proxy instead of setcap on bind failure (#5796) 2026-10-16 00:42:20 -04:00
Amnibro 8248441558 Check the TLS chain at startup and explain bind failures (#5796) 2026-10-16 00:40:00 -04:00
ancsemi 9903d9b55d Move the Discord import execute route into src/routes/import.js 2026-10-15 20:35:53 -04:00
ancsemi c46ae3143a Lock edits on protected messages and label them simply Protected 2026-10-15 12:18:31 -04:00
ancsemi 1a66c11ebb Merge branch 'security-protect' into security-fixes 2026-10-15 12:15:49 -04:00
ancsemi b324d890cc Skip protected messages in bulk deletes, scrubs and account deletion 2026-10-15 12:04:32 -04:00
ancsemi 9ecd88e1dd Refuse single deletes of protected messages and their thread heads 2026-10-15 12:02:57 -04:00
ancsemi 45a9e390d7 Trust forwarded addresses only from this machine unless in a container 2026-10-15 11:59:31 -04:00
ancsemi 6faa4f6b41 Merge branch 'security-socket' into security-fixes 2026-10-15 01:15:24 -04:00
ancsemi b054295b57 Take high scores only for known games and cap the beta signup list 2026-10-15 01:10:40 -04:00
ancsemi a16676592f Escape the reaction popout emoji and check imported reactions 2026-10-15 01:00:15 -04:00
ancsemi b0b9adc719 Cap the text fields an upload may carry 2026-10-15 00:53:53 -04:00
ancsemi e18f31dd4f Bot deletes of others' messages need the creator's delete_message 2026-10-15 00:51:49 -04:00
ancsemi ba330e2040 Invite links answer the same for any code and are rate limited per IP 2026-10-15 00:47:06 -04:00
ancsemi 1e86498e14 Accept only safe integer high scores under a per-game ceiling 2026-10-15 00:44:55 -04:00
ancsemi 1ed9232a2e Limit bot avatars to uploads or https and clean bot names 2026-10-15 00:33:23 -04:00
ancsemi 3ca1d44898 Bots without moderation delete only their own messages 2026-10-15 00:32:08 -04:00
ancsemi 48ebe911d4 Bot moderation needs the creator to still hold the permission 2026-10-15 00:31:19 -04:00
ancsemi c493c7a965 Silence bots whose creator is banned or lost access to the channel 2026-10-15 00:29:43 -04:00
ancsemi 42aef5bece Bot commands need sign-in; bot and API moderation follow rank and are audited 2026-10-15 00:25:29 -04:00
ancsemi 398f270d3e File cleanup only removes unused channel uploads 2026-10-15 00:07:39 -04:00
ancsemi d288a04124 IP bans cannot cover this machine, the banner, or higher staff 2026-10-14 23:41:21 -04:00
ancsemi 36829a67e4 Startup banner shows the LAN address and lines up its right edge (#5572) 2026-10-14 22:56:13 -04:00
ancsemi 0b01f9553f Repair double-encoded box lines and emojis in server.js console output 2026-10-14 22:55:56 -04:00
ancsemi 45140c1ef2 Discord import puts threads in their channel and forum posts in a forum (#5772) 2026-10-13 19:20:11 -04:00
ancsemi 05f329a563 Ferry keeps Discord ids and carries replies from chat threads (#5773) 2026-10-13 19:20:11 -04:00
ancsemi e593481956 Imported Discord messages belong to no Haven account (#5771) 2026-10-13 19:12:49 -04:00
ancsemi f617b97b0c Keep downloaded Flash games in the data folder so they survive updates 2026-10-12 00:56:09 -04:00
ancsemi 92738c2b09 Replace em dashes in server code comments and scripts 2026-10-10 16:08:55 -04:00
ancsemi 0b8c04e708 Replace em dashes in the English UI text and messages 2026-10-10 16:08:54 -04:00
ancsemi 0d96d9858c Server side of Blog mode for forums (#5742)
A new forum_blog switch on channels (off by default), turned on and off
through the same channel permission toggle as Forum mode and NSFW, so only
someone who can change the channel's settings can flip it. The channel list
carries it.

With it on, a topic belongs to its author. A reply in the topic's thread is
part of the post when the topic author's own account wrote it (and it is not
a bot or relayed message); everything else is a comment. Nothing is stored
per message, so turning it on or off needs no migration and changes nothing
on disk. The rule lives in src/forumBlog.js.

The server works the answer out and sends it: thread history marks each reply
post_part and says the topic is a blog post, a new reply carries post_part,
and topic cards and live thread updates carry a comments count alongside the
reply count. user_id comes from the signed-in socket, so nobody can make
their reply count as the author's, and bot replies are always comments.
2026-10-09 13:23:19 -04:00
Mattandbo0sted 802909c1c7 Push subscriptions honor HAVEN_ALLOW_PRIVATE_CALLBACKS like bot callbacks do (#5743)
The push subscribe routes (socket and /api/push/subscribe) refuse endpoints
that resolve to a private address, but unlike bot callbacks they ignored the
HAVEN_ALLOW_PRIVATE_CALLBACKS opt-in. A self-hosted ntfy for UnifiedPush on
the same Docker network as Haven resolves to a private address from inside
the container, so every new registration was dropped. Existing subscriptions
kept working because the check only runs on subscribe, so it surfaced only
once a phone got a new endpoint.

The socket route dropped refused registrations silently, leaving the device
without notifications and nothing in the logs. Both routes now log why a
subscription was refused. .env.example notes that the setting covers push
endpoints too.

Co-authored-by: bo0sted <mc526+github@proton.me>
2026-10-09 10:29:07 -04:00
ancsemi 938204b768 Move the server template HTTP routes out of server.js into src/routes/serverTemplate.js; no change in behavior (#5732) 2026-10-05 18:44:19 -04:00
ancsemi 88244097f5 Server template imports now keep emojis and stickers within the server's size limits, copy only the images the template uses, and are refused when the server is low on disk space, like uploads (#5732) 2026-10-05 18:44:19 -04:00
ancsemi 4e5c712066 Remove an unused image metadata import from server.js and put back its UTF-8 byte order mark (#5732) 2026-10-05 18:44:19 -04:00
Amnibroandancsemi 662534f976 Server templates: export a server's branding, categories, channels, roles and setup posts as one file, and import one from Settings or tools/template.js (#5732)
Admins download the template under Settings > Admin > Server Template, and import
one with a preview of what it will create or update. Add missing leaves existing
channels, roles and settings alone; Replace layout updates them to match. Neither
mode deletes anything. Members, messages, DMs, keys, invite and channel codes,
webhook tokens and secret settings are never exported. Settings go through an
allowlist, and imports are validated strictly: sizes, names, references, image
types and checksums.

The channel-name rule, built-in theme list, automod word and escalation checks
and image sniffing now each live in one place, since the template code shares
them.

Co-authored-by: ancsemi <ancsemi@gmail.com>
2026-10-05 18:00:22 -04:00
Mattandbo0sted fa251413fe Admins can choose whether self-destructed attachments are kept (#5731)
When a self-destructing message expires, its files have always been moved
to deleted-attachments for the retention window, so a moderator can
recover something abusive that was posted on a short timer. That is the
right default for community servers, but small servers where everyone
trusts each other may want those files gone for good when the timer
runs out.

A new "Keep self-destructed attachments" toggle sits under "Keep deleted
files for (days)" in Admin settings, Cleanup. It is on by default, so
nothing changes for existing servers. When it is on, the files are held
for the same retention period as other deleted attachments. When it is
off, the sweep deletes them directly instead of holding them.

Only an explicit off deletes for good. If the setting can't be read (for
example, a busy database), the files are still held, so a read failure
never destroys evidence. The server only accepts true or false for the
setting.

This commit also fixes a loophole that let anyone stop a self-destructing
message's attachments from being removed, just by quoting the message or
linking its attachments before the timer ran out. The message went away,
but its attachments stayed in uploads for good.

Self-destructing messages can no longer be replied to or quoted by
anyone, mods included, so the sender can't accidentally undo their own
self-destruct either. The server also drops a reply to one. Mods can
still keep a message by pinning or protecting it, which removes the
timer as before and allows replies and quotes again. When the timer
runs out, a link to the attachments posted in any other message no
longer keeps them. The link text stays, but it stops working.

Co-authored-by: bo0sted <mc526+github@proton.me>
2026-10-05 17:16:39 -04:00
ancsemi 76ccfc42bb Self-destructing messages keep the usual deleted-file window (#5725)
Expired attachments were unlinked at once, so something abusive posted on
a 30 second timer left moderators nothing to recover. They now go the way
a deleted message's files do: only the sender's own, kept while a quote or
a profile still uses them, and into deleted-attachments for the retention
window. The send dialog says so.
2026-10-03 23:17:02 -04:00
Mattandbo0sted 0447b0cdd1 [feat] Self destruct on individual messages (#5725)
* Self-destructing messages

A flame toggle next to the mic in channel composers. Sending with it on
asks how long the message lives (1 minute to 24 hours, in minutes or
hours), then the message and the attachments sent with it are deleted
when the time runs out. The sender's own files are removed for good,
skipping deleted-attachments. Each message shows "Self-destructing in
..." under its content until then.

On the server one timer waits for the soonest deadline instead of
polling: it catches up at startup, sleeps when nothing is left, wakes
when a new self-destructing message is sent, and moves on when the
message it waits for is deleted early.

* Self-destruct modal: show how long until it deletes as you type

Updates on every keystroke and when switching between minutes and hours. Spelled out exactly ("1 hour, 30 minutes") rather than rounded, and in the reader's language.

* Self-destruct: allow timers as short as 30 seconds

* Self-destruct: voice messages too

With the flame on, clicking the mic asks how long first and only starts
recording once a time is confirmed; backing out records nothing and keeps
the flame on. The timer counts from when the recording is sent, and
turning the flame off while recording sends it as a normal message.

* Self-destruct: GIFs follow the flame too

In a channel a GIF used to go straight to the server, so it ignored the
self-destruct toggle. With the flame on it now goes through the
composer's send, the way DM GIFs already do, so it asks how long first.

---------

Co-authored-by: bo0sted <mc526+github@proton.me>
2026-10-03 23:12:25 -04:00
ancsemi 695604bbb1 Move extension updates into their own files
A pure move: the two admin routes leave server.js for
src/routes/extensionUpdates.js (io is read through late, as backup.js does,
since it does not exist yet at registration), and the Settings UI leaves
app-admin-controls.js for a new app-extension-updates.js mixin. Proved with
an identical client method set and identical route handlers.
2026-10-03 15:46:55 -04:00
Josh Stevenson-WoodsandJosh Stevenson-Woods e84e562b53 Add manual plugin and theme updates (#5588)
* docs: propose extension release and blocklist contracts

* feat: add manual plugin and theme updates

---------

Co-authored-by: Josh Stevenson-Woods <5860234+jkswoods@users.noreply.github.com>
2026-10-03 15:35:49 -04:00
Matt eb220e9189 Outgoing proxy support for servers behind a proxy (#5710)
For servers that can only reach the internet through a proxy: set
https_proxy, http_proxy and no_proxy in .env and Haven's outgoing requests
(link previews, the media proxy, bot callbacks, push notifications, update and
DDNS checks) go through it. Only http:// proxy addresses are supported, and
voice is unaffected.

With no proxy set, nothing changes. With one set, Haven warns at startup that
the proxy becomes part of its security: requests through it cannot be pinned
to the address Haven checked, so the proxy should be limited to the
destinations Haven needs. .env.example and GUIDE.md ("Outgoing Proxy") explain
it. Proxy passwords are hidden in the log, HTTPS certificates are checked
against the real target, and proxied requests time out and can be cancelled.
2026-10-02 12:25:26 -04:00
ancsemi 2d380d31bd Ferry forums, review fixes: marked Discord authors in threads, moderation that sticks, NSFW held back
- Discord replies relayed into a Haven thread are shown as "[BOT] name",
  live and in thread history, the way channel history already shows them,
  so a Discord nickname cannot pass for a Haven member's name.
- A Discord post update is passed on only for what actually changed on
  Discord (title, tags, lock). Discord archiving or unarchiving a quiet post
  no longer reopens, retitles or retags a topic a Haven moderator changed.
- A topic marked NSFW is not sent to a Discord forum that is not
  age-restricted; the author is told and the pairing shows why.
- The test hooks that point Ferry at a stand-in Discord only work when
  NODE_ENV is test.
2026-10-02 08:03:50 -04:00
ancsemi 4868ac2621 Split server.js: seven areas move to src/routes/ unchanged
server.js goes from 6,600 lines to 3,700. Profile pictures and personas,
image and file uploads, sounds/emoji/stickers, backup and restore, link
previews and the media proxy, the Discord import, and the background jobs
(auto-cleanup, auto-backup, dynamic DNS, updates) each live in their own file
under src/routes/, registered from the same spot in server.js they used to
occupy, so the order of every route and middleware is unchanged.

The code was moved, not rewritten. A scope analysis of server.js decided what
each file needs and gives back; the only edits are mechanical ones it placed:
- values server.js creates later or reassigns while running (the socket
  server, bot audio) are read through a small `late` object when used, so a
  route never holds a stale copy
- relative require() paths and __dirname are adjusted for the new folder

Checked by: rebuilding the old server.js from the new files (every line the
same apart from indentation, every template literal byte-identical); the
route table (all 126 layers, same paths, methods, middleware and order); each
of the 107 route handlers' source matching the original once the rewrites
above are undone; the full test suite; and a live run hitting endpoints from
every moved file.
2026-10-02 07:52:28 -04:00
ancsemi 6d9a76eec5 Every silent catch in the server now either reports the error or says why ignoring it is safe
Went through all of them (about 220 across server.js and src/). The ones
that could leave data, settings or files wrong without anyone knowing now
log a short warning: failed file moves and deletes after a delete or
restore, backup entries that could not be added, startup steps like the
sticker folder or the Referrer-Policy, auto-role grants on join, audit log
writes, attachment tags dropped on send, webhook delivery status, the AFK,
temp-channel and disk-space timers, DM call and push lookups, Discord import
files that could not be parsed, Ferry's loop guard and automod checks, the
media cache folder and the one-time data folder migration. Ones that repeat
on busy paths log at most once every few minutes.

The rest are expected failures (a temp file already gone, a socket already
closed, untrusted input with a fallback) and each now carries a comment
saying so instead of a bare catch {}. Two guards that could never fire were
removed. No bare catch {} is left in the server code.
2026-10-02 07:36:58 -04:00
ancsemi 8c3ae6edea Checks that used to let things through when an error was swallowed now refuse instead
Found while going through every silent catch in the server:
- a personal "deny" on a permission was skipped if reading it failed, so a
  role could still grant it (server routes, socket permission checks, and the
  list that decides what a non-admin may hand out to others)
- deleting your own messages, singly or in bulk, went ahead when a deny on
  that could not be read
- a banned account was given voice relay (TURN) credentials if the ban
  check errored
- link previews went ahead for hosts the admin had not allowed if the
  automod gate errored, which is the IP leak that gate exists to stop
- the FCM privacy switch stayed on if it could not be read at startup
- a login could be issued with no expiry if the session length setting
  could not be read; it now falls back to 7 days
- someone who turned activity sharing off, or went invisible, could have
  their activity shown if their preference could not be read
- a failed read of the IP ban list cleared every IP ban for 30 seconds; the
  last known list is kept instead
- a backup restore overwrote the live database even when saving the safety
  copy failed, and that copy could miss writes still in the WAL; it now
  checkpoints first and stops if the copy cannot be made
- a corrupt android-beta-signups file was read as empty and the next signup
  wiped the rest
- failing to remove ADMIN_RESET_PASSWORD from .env (which resets the admin
  password on every start) is now reported with what to do
2026-10-02 07:35:20 -04:00