From 22f9e1214146eb8bd0468c2ddd22aa94d4d8a379 Mon Sep 17 00:00:00 2001 From: glass beadian Date: Tue, 13 Oct 2026 09:49:57 +0000 Subject: [PATCH] Login: bare sign-in page at /login; Telegram bridge connection guide --- apps/web/src/app/login/page.tsx | 72 ++++++++++++++++++++++++-- docs/telegram-bridge-guide.md | 90 +++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 docs/telegram-bridge-guide.md diff --git a/apps/web/src/app/login/page.tsx b/apps/web/src/app/login/page.tsx index d0f8cc4..2023d24 100644 --- a/apps/web/src/app/login/page.tsx +++ b/apps/web/src/app/login/page.tsx @@ -1,9 +1,71 @@ -import { redirect } from "next/navigation"; +"use client"; + +import { useState, type FormEvent } from "react"; +import Link from "next/link"; +import { Button, Card, Input } from "../../components/ui/primitives"; +import { api, errorMessage } from "../../lib/api-client"; +import { useAuth } from "../../lib/auth-store"; /** - * /login — the sign-in door. The onboarding page holds both forms; this route - * deep-links into sign-in mode (register stays the default at /signup). + * /login — bare sign-in: username and password, nothing else. Account creation + * lives at /signup; collective joining happens with an invite link. */ -export default function LoginRedirect(): React.ReactElement { - redirect("/onboarding?mode=signin"); +export default function LoginPage(): React.ReactElement { + const { login } = useAuth(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + + async function submit(e: FormEvent): Promise { + e.preventDefault(); + setBusy(true); + setError(null); + try { + await api.post("/auth/login", { username: username.trim(), password }, { redirectOn401: false }); + window.location.assign("/streams"); + } catch (err) { + setError(errorMessage(err)); + setBusy(false); + } + } + + return ( +
+ +
+

Sign in

+

Tactical Media Broadcast Hub

+
+
void submit(e)}> +
+ setUsername(e.target.value)} + placeholder="username" + autoComplete="username" + autoFocus + /> + setPassword(e.target.value)} + placeholder="Password" + autoComplete="current-password" + /> +
+ {error !== null &&

{error}

} +
+ + Create an account + + +
+
+
+
+ ); } diff --git a/docs/telegram-bridge-guide.md b/docs/telegram-bridge-guide.md new file mode 100644 index 0000000..d823426 --- /dev/null +++ b/docs/telegram-bridge-guide.md @@ -0,0 +1,90 @@ +# Telegram bridge — connection and admin guide + +Status: bridge live on the chat CT (mautrix-telegram, appservice registered with +tuwunel). Bot: **@occupymedia_bot**. This page is the operator + end-user guide. + +## What it does + +Each collective's **open room** can be attached to a **Telegram group**. +Outsiders talk from Telegram with no Matrix account; collective members answer +from Element. Everything in the open room still flows through the archive +(shares into the group's files folder, indexed, searchable). + +Closed production rooms are **never** bridged — they are end-to-end encrypted +and the bridge has no keys. This is by design. + +## For collective admins + +1. Create the Telegram group (public link recommended — it's the outsiders' + door) or use an existing one. +2. Add **@occupymedia_bot** to the group and **promote it to admin**. +3. In BotFather: `/setprivacy` → @occupymedia_bot → **Disable** — without this + the bot cannot read group messages. +4. From Matrix (Element), in the collective's open room, a bridge admin sends + the bridge command to link the room. The bridge bot account + (@telegrambot:chat.occupymedia.cc) then relays both ways. + +Contribution policy reminder (disclosed at join time): messages and files that + outsiders post through the open room are archived into the collective's + library the same as member posts. Telegram participants are subject to the + same moderation (redaction propagates; archive entries get marked + unavailable and links die). + +## For outsiders (Telegram side) + +- Join the collective's public Telegram group. +- Everything you post is visible to the collective and archived in their + public library. Same rules as posting to any public channel of the + collective. +- You do not need a Matrix account. No account is created on this server for + you beyond a relay placeholder. + +## How it works (for operators) + +- The bridge is mautrix-telegram on CT 152, compose project `occ-chat`, + service `mautrix-telegram` (profile `bridges`), config + database in the + `occ-chat_mautrix_telegram_data` volume. +- It registers with tuwunel as an appservice (inline declaration in + `/opt/occupymedia/tuwunel.toml`, `[appservice.telegram]` — as_token, + hs_token, sender_localpart `telegrambot`, user namespaces for + `@telegrambot` and `@telegram_*` ghosts). +- Telegram API credentials: `api_id` / `api_hash` from the operator's + my.telegram.org app "OccupyMediaBridge" + the BotFather token for + @occupymedia_bot, all in the bridge's `config.yaml` (mode 600, in the data + volume — never in git). +- Appservice registration file: `/opt/occupymedia/appservices/registration.yaml` + (mounted read-only into tuwunel at `/appservices`, loaded via + `appservice_dir`). + +## Operations + +| Task | How | +|---|---| +| Restart bridge | `docker restart occ-chat-mautrix-telegram-1` (CT 152) | +| Logs | `docker logs -f occ-chat-mautrix-telegram-1` | +| Config | volume `occ-chat_mautrix_telegram_data` → `config.yaml` | +| Re-register appservice with tuwunel | admin room: `!admin appservices list` / `register` (or the inline TOML section — both load) | +| Known-good boot | logs end with the bridge serving on :29317 and no `Configuration error` | + +## Failure modes seen in practice + +- `as_token was not accepted` → the appservice registration is not loaded by + tuwunel (check the inline section and `/appservices` mount), or the tokens + were regenerated on one side only. +- `invalid permissions level X` → this mautrix generation only accepts + `relay`, `commands`, `user`, `admin` in `bridge.permissions` — "relaybot" + and "full" are not valid. +- `disk quota exceeded` at container start → host kernel keyring quota + (LXC+Docker); raised + persisted on the host in + `/etc/sysctl.d/99-lxc-keys.conf`. +- oCIS `strconv.ParseInt ""` on uploads → a client sent a chunked PUT without + Content-Length; fixed in the archive bot (buffer + explicit + content-length). + +## Roadmap + +- Telegram login (userbot) for the operator's own account — richer bridging + (history, DMs), at the cost of operating a userbot (ban risk if it behaves + spammy). Not enabled; the bot path covers the collective use case. +- Per-collective Telegram groups auto-created at collective creation. +- Contribution-policy banners posted into bridged groups by the guide bot.