Commit Graph
24 Commits
Author SHA1 Message Date
ancsemi 45a9e390d7 Trust forwarded addresses only from this machine unless in a container 2026-10-15 11:59:31 -04:00
ancsemi d9114dc560 Document OUTBOUND_PROXY_RESOLVES_NAMES in .env.example 2026-10-15 00:41:06 -04:00
ancsemi fb0d6fb18d Replace em dashes in the docs and web pages 2026-10-10 16:08:55 -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
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
Matt 580b67e450 GIF search supports KLIPY, with a preferred provider setting (#5629)
KLIPY joins GIPHY and Tenor as a GIF provider. Set KLIPY_API_KEY to enable it, and PREFERRED_GIF_SEARCH (klipy, giphy or tenor) to pick which provider serves the picker when more than one key is set. With no preference the server tries GIPHY first, then KLIPY, then Tenor. Existing GIPHY and Tenor setups are unchanged. The picker footer credits whichever provider served the batch.
2026-09-10 22:54:03 -04:00
b3cd0b4f0b GIF search: GIPHY is the supported provider again, not Tenor (#5546)
5472 preferred Tenor because GIPHY looked dead. That was backwards: GIPHY still works, and Tenor is no longer offered for new setup. Existing Tenor keys still serve if no GIPHY key is set.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: ancsemi <ancsemi@gmail.com>
2026-09-01 21:59:49 -04:00
Matt def22e3fff [big feat] Search overhaul (#5533)
* Rework message search into a persistent overlay panel

Search results now open as an overlay on the right side over the member
list, the way Discord does it. The panel stays open across channel
switches and only closes when you close it yourself. Public channels
share one panel while each DM keeps its own.

Results paginate at 25 per page. The panel clears and asks you to
refresh when a channel or one of your roles changes, so it never shows
messages you can no longer see. A collapsed sidebar expands
automatically while the panel is open.

* Make message search global with SQLite full text search

Search now spans every channel you can see instead of just the current
one, backed by an FTS5 index that stays in sync with messages through
triggers. Results are scoped to your channel memberships on every query,
so you never see anything you don't have access to. in:#channel narrows
to one channel, from: and has: still work.

The tokenizer is admin configurable through HAVEN_SEARCH_TOKENIZER and
reconciled at boot: unicode61 by default (closest to Discord), with
porter for stemming and trigram for substring matching. Changing it
rebuilds the index once on the next restart. Results come back 25 per
page with a total count, sorted newest first.

* Add a filter picker and cross-channel results to search

Clicking the search box opens a filter popover with from:, in: and has:
pickers. Each lists what you can see (members, channels, media types),
rendered client side with a prefix filter box, and clicking one appends
the filter to the search box. A filter button next to the box toggles
the popover so you can reopen it without closing search.

Channel names are not unique, so in:#code selects a channel by its code
while in:name still matches by name. The picker fills the code form so
duplicate names are never ambiguous.

Search results now show the channel each message came from as
#name (code) above the author, and clicking a result switches to that
channel before jumping to the message so cross-channel results work.

* Add date, pinned, and audio filters plus a sort control to search

The filter popover gains before:, after: and during: date pickers, a
pinned toggle, and audio in the has: list. before/after take a boundary
date and during matches a single day. A sort row lets you order results
by newest, oldest, or most relevant, which the search engine already
supported but the UI never exposed.

All of these run server side against the message index, so they stay
scoped to the channels you can see.

* Guard against stale search results, add a loading state and recents

Each search request carries a sequence token that the server echoes
back, and the client only renders the response matching its latest
request. A slow earlier query can no longer overwrite newer results.
A spinner shows while a request is in flight.

Recent searches are kept in local storage and offered at the top of the
filter popover when the box is empty. They save on Enter, so the list
holds real searches rather than the partials typed on the way there.

* Render search results as real messages and rate limit search

Search results now render through the message formatter, so images,
uploaded video and audio, spoilers, mentions and links look the same as
they do in chat. Audio uses preload=none so nothing loads until play.

Page links are not unfurled automatically. Each link gets a Load button
that renders just that embed through the existing preview scheduler and
cache, so a page of results never fires a burst of preview fetches.

Right clicking a result opens a Copy link and Delete menu. Delete targets
the result's own channel, and the row is removed only once the server
confirms the delete. Results that head a thread show a reply count badge.

The bottom pager gains a page number input so you can jump straight to a
page instead of clicking through the arrows one fetch at a time.

Search also gets its own per account rate limit of 10 requests per 10
seconds. When it trips the client keeps its current results and shows a
toast.
2026-08-27 00:28:47 -04:00
ancsemi 8b4bc00336 Allow bot callbacks to private addresses behind an env var (#5518) 2026-08-21 20:36:04 -04:00
ancsemi 4bc9da66b1 Point people at fullchain.pem when setting SSL_CERT_PATH by hand 2026-08-21 20:22:55 -04:00
ancsemi 34a01e87c9 Keep disk headroom in reserve so a full volume stays recoverable (#5505)
Reported by KentuckyFriedBlyat. A full data volume does not just stop
uploads, it wedges the instance: SQLite needs to write in order to delete
a row, so an admin cannot clear the attachments that filled the disk. The
delete is a write, and the write fails. The only way out was to grow the
disk from underneath the server.

Haven now refuses to hand out the last of the space. Every upload route
checks, before multer streams anything to disk, whether accepting the
largest currently permitted file would cut into a reserve (512 MB by
default, HAVEN_DISK_RESERVE_MB to change it). If it would, the upload is
refused with a 507 and a message pointing at the real problem, and the
server logs one warning every five minutes rather than one per attempt.

The check reads free space with statfs, cached for five seconds so a
burst of uploads is not a burst of syscalls. Where the platform will not
report free space the guard fails open, so nothing changes for anyone it
cannot measure.

Text posting is deliberately untouched: messages are tiny next to the
reserve, and a server that stops accepting chat is no way to tell people
their disk is full. The emergency-mode parts of the report (admin-only
messaging, automatic warning posts and DMs) are not built here.
2026-08-18 21:04:14 -04:00
birdcrazy 2feb1e53be Fix OIDC trailing slash handling for providers that require it (#5503)
The OIDC issuer URL was having its trailing slash stripped, which broke providers like Authentik that publish the issuer with one. The issuer is now left as entered, and the discovery path is built correctly either way.

Also documents OIDC_CLIENT_SECRET in .env.example and docker-compose.yml.

Reported by @jesjhoward in #12 and @RCCore in #5501.
2026-08-18 20:56:53 -04:00
Matt 84d7a7959e [feat] Build the emoji picker from the full Unicode list (#5490)
* Build the emoji picker from the full Unicode list

The picker's list was hand-curated in app.js and missing plenty of emoji.
Instead of topping it up by hand, the server now builds the whole
categorised list from Unicode's own emoji-test.txt and serves it, and the
client renders the glyphs with the OS font so they match whatever platform
the user is on.

On boot the server keeps a copy of emoji-test.txt in the data dir, fetched
from the draft directory so it tracks the latest release. It refreshes when
the copy is over a month old, and a committed 16.0 file is the fallback when
there's no internet, so the server always has an emoji list. A single stat,
at most one request, at most one write per boot.

Parsing keeps fully-qualified and minimally-qualified entries (deduped by
VS16), drops unqualified and component rows, and strips skin-toned variants
down to the base glyph. Hair emoji come through as their own base glyphs,
so the existing skin-tone code layers a tone on top the way Discord does
(person red hair plus a tone). The skin-tone modifier-base set is derived
from the same file. The built-in list stays as an offline fallback, and the
bundled flag images are kept since Unicode flags don't render on every OS.

* Make the Unicode emoji auto-update opt-in and pull from the published release

Addresses review feedback on the emoji-picker PR:

- Fetch from Public/emoji/latest instead of Public/draft. The draft
  directory carries Unicode's in-progress version, whose newest glyphs
  no shipped OS font can render yet (empty boxes in the picker).

- Gate the monthly refresh behind an admin toggle under Settings ->
  Emojis, off by default. UNICODE_EMOJI_AUTO_UPDATE in the environment
  overrides it (true/false) for locked-down or no-outbound deployments.
  Enabling the toggle refreshes immediately instead of waiting for boot;
  when off, Haven serves the on-disk copy, falling back to the bundled
  16.0 set if one was never downloaded.

- Fix jumbo sizing for skin-toned emoji: tone modifiers (1F3FB-1F3FF)
  are Emoji_Modifier, not Extended_Pictographic, so they slipped past
  _isEmojiOnly and toned emoji rendered at normal size instead of large.
2026-08-13 00:25:47 -04:00
Amnibro 64e54bc0dc GIF search: add Tenor as the primary provider (GIPHY kept as legacy) (#5472)
GIPHY no longer issues API keys to new applications, so fresh Haven
installs cannot configure GIF search at all — the picker dead-ends at
the GIPHY signup step. Add Google's Tenor (v2) as the preferred
provider and keep existing GIPHY keys working unchanged.

- server.js: getGifProvider() resolves tenor_api_key (DB setting or
  TENOR_API_KEY env) first, then the legacy giphy key. Both providers
  normalize to the { id, title, tiny, full } shape the client already
  consumes, and responses now carry which provider served them.
- admin settings: tenor_api_key joins the allowlist, the sensitive-key
  redaction list, and gets the same length validation as the giphy key.
- picker setup guide: Tenor instructions by default with a provider
  selector (GIPHY stays available for admins who already hold a key);
  the footer reports the provider actually serving results.
- inline rendering: media/c.tenor.com joins the giphy hosts in the
  image whitelists so sent GIFs render as images, not links.
- .env.example + strings updated; locales pass validate-locales.

Verified against a live server: tenor key -> provider:tenor results,
giphy-only key -> provider:giphy, no key -> 501 gif_not_configured
(admin setup guide).
2026-08-02 20:52:49 -04:00
KevonLin e9929b40ad docs: add STEAM_API_KEY to docs, add override.yml to .gitignore (#5447)
- .env.example: add STEAM_API_KEY entry with usage docs and link to Steam dev portal
- .gitignore: add docker-compose.override.yml (local per-deployment config)
- README.md: add STEAM_API_KEY to config table with descriptions
2026-07-23 20:33:42 -04:00
KevonLin bb568cb8aa feat: add PUBLIC_URL env var for OAuth/OpenID callback URLs (#5443)
When Haven runs behind Docker port mapping (8080->3000), a reverse
proxy that strips the port from the Host header, or a Cloudflare
Tunnel, the server can't reliably guess its own public-facing URL.
This causes Steam and Spotify callback URLs to be wrong.

Add PUBLIC_URL environment variable. When set, baseUrl() uses it
explicitly instead of trying to derive from req.protocol + Host.
Covers all callback URL construction in connectRoutes.js (Steam
OpenID return_to/realm and Spotify OAuth redirect_uri).
2026-07-19 19:01:30 -04:00
ancsemi 6d2a4d25e5 ddns: auto-update DuckDNS/Cloudflare/generic so ISP IP changes don't break the server 2026-05-31 00:11:48 -04:00
ancsemi 6e756b6939 v2.9.7 — Remove Google STUN, add STUN_URLS env var
- Replace Google STUN servers with open-source defaults (stunprotocol.org, nextcloud.com)
- Add STUN_URLS env var for custom STUN server configuration
- Update .env.example with STUN_URLS documentation
2026-04-09 00:04:49 -04:00
ancsemi 0b2deb42fa feat: FORCE_HTTP mode for reverse proxy (#48) + auto-assign default roles (#14)
- Add FORCE_HTTP=true env option to skip built-in SSL (for Caddy/nginx/Traefik)
- Update startup scripts (bash, bat, Docker) to respect FORCE_HTTP
- Disable HSTS header when FORCE_HTTP is active
- Add auto_assign column to roles table with DB migration
- Default 'User' role is auto-assigned on registration
- Admin UI: toggle auto-assign per role, visual indicator
- join-channel auto-assigns flagged roles to new members
- Fix window.app reference for desktop voice hook
2026-02-21 12:28:20 -05:00
ancsemi 61a237f521 feat: configurable TURN server support for voice/screen sharing over internet
Adds TURN_URL, TURN_SECRET, TURN_USERNAME, TURN_PASSWORD env vars.
New /api/ice-servers endpoint serves STUN + TURN config to authenticated clients.
Supports both coturn shared-secret (time-limited credentials) and static auth.
Client fetches ICE config on init and refreshes before each voice join.

Closes #37
2026-02-19 10:38:40 -05:00
ancsemi 2b8c04cb22 Discord-style status indicators on avatars, fix .env Tenor refs, sync website
- Add: Status indicator dots overlaid on user avatars (green=online, yellow=away, red=dnd, grey=offline/invisible) with dark border cutout, responsive sizing for mobile
- Fix: .env.example still referenced Tenor API  updated to GIPHY with correct URL
- Fix: CSS comment 'Tenor key'  'GIPHY key'
- Sync: website/index.html replaced with canonical docs/index.html
- Add: Help & Support footer link to docs/index.html
- Add: Deploy checklist note in CHANGELOG.md
2026-02-12 22:54:08 -05:00
ancsemi d34e165585 feat: Tenor GIF integration with search picker
- GIF button next to emoji picker opens search/browse panel
- Server-side proxy keeps Tenor API key hidden from clients
- Trending GIFs shown by default, live search with debounce
- Click a GIF to send it as an inline image in chat
- Tenor URLs auto-detected and rendered as inline images
- CSP updated to allow media.tenor.com images
- TENOR_API_KEY added to .env.example (optional, free)
- Full CSS theming for GIF picker panel
2026-02-11 17:24:02 -05:00
ancsemi 5a11a910b3 feat: move all user data to external directory (%APPDATA%\Haven or ~/.haven)
- Database, .env, certs, and uploads now stored outside the code directory
- Auto-migration moves existing data from old locations on first launch
- SSL certs auto-detected in data dir (no .env config needed)
- New src/paths.js module as single source of truth for all data paths
- HAVEN_DATA_DIR env var for custom data location override
- Updated Start Haven.bat, start.sh, README, GUIDE, .env.example
- Bumped version to 1.1.0
2026-02-11 16:52:46 -05:00
ancsemi b7b8b1bb86 v1.0.0 Haven public release 2026-02-11 00:15:56 -05:00