Skip to content

feat(auth): Continue with Discord button + session-handoff on OAuth authorize - #9

Closed
bryanchriswhite wants to merge 104 commits into
stagingfrom
feat/discord-login-handoff-web
Closed

feat(auth): Continue with Discord button + session-handoff on OAuth authorize#9
bryanchriswhite wants to merge 104 commits into
stagingfrom
feat/discord-login-handoff-web

Conversation

@bryanchriswhite

Copy link
Copy Markdown

Frontend half of the Discord login (operator-approved, validator-APPROVED). Pairs with censer-backend MR!53 (one-time handoff-code session bridge). Inert until the backend [api.discord_oauth] is configured (button hits a 404 while dark).

Changes — packages/client/components/auth/src/flows/FlowOAuth2Authorize.tsx

  • "Continue with Discord" button in the not-logged-in state → GET /auth/discord/login?return=<current authorize URL> (preserves the OAuth2 params across the round-trip).
  • onMount handoff exchange: if ?discord_handoff=<code> is present → POST /auth/discord/complete {code}:
    • Success → establishes the session via state.auth.setSession(...) + LoginUncached transition (the exact tail of ClientController.login), strips the param, consent renders.
    • MFA → reuses the existing mfa_flow modal + /auth/session/login ticket exchange.
    • Error → falls back to the login form.

Notes

  • Validator confirmed the frontend faithfully reuses the client's session-establishment + MFA paths; token never lands in a URL (only the opaque one-time code).
  • ⚠️ Typecheck not run locally (uninitialized submodules) — relying on CI. Please confirm the pipeline typechecks.
  • Based on staging, which currently has inline login only; the inline register/verify-email work (chat-szg1) lives on a separate branch — these integrate cleanly but may need a rebase when both land.

Beads: chat-szg1 / chat-76ab.

luthes and others added 30 commits February 17, 2026 19:36
…chat#695)

im not sure this is necessary, or it gifbox will stick around because of
tenor 🤷 could probably be separate from docker builds as well

Signed-off-by: Steven Luther <steven@lutherlabs.com>
Co-authored-by: marcy <69540471+reeesespuffs@users.noreply.github.com>
Signed-off-by: Shams <shams.tech.dev@proton.me>
Co-authored-by: Shams <shams.tech.dev@proton.me>
Co-authored-by: marcy <69540471+reeesespuffs@users.noreply.github.com>
* feat: add button to rename categories

Signed-off-by: Amy <amy+git@amogus.cloud>

* chore: generate i18n catalog

---------

Signed-off-by: Amy <amy+git@amogus.cloud>
Co-authored-by: Stoat CI <stoat-ci@users.noreply.github.com>
Co-authored-by: marcy <69540471+reeesespuffs@users.noreply.github.com>
* chore: add delete bot button back to ViewBot component

Signed-off-by: Amy <amy+git@amogus.cloud>

* chore: generate i18n catalog

---------

Signed-off-by: Amy <amy+git@amogus.cloud>
Co-authored-by: Stoat CI <stoat-ci@users.noreply.github.com>
Co-authored-by: marcy <69540471+reeesespuffs@users.noreply.github.com>
* feat: add runtime env injection script and server package

Signed-off-by: Steven Luther <steven@lutherlabs.com>

* fix: remove revolt env vars

Signed-off-by: Steven Luther <steven@lutherlabs.com>

* ci: add GitHub Actions workflow for Docker image build and push

Signed-off-by: Steven Luther <steven@lutherlabs.com>

* improvement: rewrite to use native node packages

Signed-off-by: Steven Luther <steven@lutherlabs.com>

* improvement: bump Docker base image from Node 22 to Node 24 LTS

Signed-off-by: Steven Luther <steven@lutherlabs.com>

---------

Signed-off-by: Steven Luther <steven@lutherlabs.com>
Fix Docker Build for AMD64

Signed-off-by: Chris Hultin <chrishultin@google.com>
* display Report dropdown in correct pos

Signed-off-by: Jade3375 <floodlockgames9@gmail.com>

Signed-off-by: Jade3375 <floodlockgames9@gmail.com>
Co-authored-by: Stoat CI <stoat-ci@users.noreply.github.com>
Co-authored-by: Christopher Hultin <chrishultin@google.com>
… in chat (stoatchat#948)

fix: incorrect i18n strings causing client lockup

Signed-off-by: Jade3375 <floodlockgames9@gmail.com>
* feat: Enable video and screenshare with an env variable for self host

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* feat: Enable fullscreening video and screen share streams

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Env var works now, camera view is disabled when camera turned off

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Leave fullscreen when a user stops their camera stream

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: run linter

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Access this env var safely as it may not exist

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Only attempt to exit fullscreen if in fullscreen

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

---------

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
Co-authored-by: Christopher Hultin <chrishultin@google.com>
* fix: ignore deleted role IDs

Signed-off-by: Matthew R. <matt@capaz.dev>

* fix: omit indicators for deleted roles

Signed-off-by: Matthew R. <matt@capaz.dev>

---------

Signed-off-by: Matthew R. <matt@capaz.dev>
Co-authored-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
…hlighting (stoatchat#796)

* feat: add support for all possible languages for codeblock syntax highlighting

Signed-off-by: guiza <guiza@umich.edu>

* Fix formatting in index.tsx

Signed-off-by: Christopher Hultin <chris.hultin@gmail.com>

---------

Signed-off-by: guiza <guiza@umich.edu>
Signed-off-by: Christopher Hultin <chris.hultin@gmail.com>
Co-authored-by: Christopher Hultin <chrishultin@google.com>
Co-authored-by: Christopher Hultin <chris.hultin@gmail.com>
* feat: start Stoat in tray on startup

Signed-off-by: Mihai <cristian@mihaimuresan.com>
Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>

* chore: generate i18n catalog

Signed-off-by: Mihai <cristian@mihaimuresan.com>
Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>

* chore: generate i18n catalog

Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>

---------

Signed-off-by: Mihai <cristian@mihaimuresan.com>
Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>
Co-authored-by: Mihai <cristian@mihaimuresan.com>
Co-authored-by: Stoat CI <stoat-ci@users.noreply.github.com>
Signed-off-by: sirlan <sirlan@protonmail.com>
Co-authored-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
Signed-off-by: izzy <me@insrt.uk>
Signed-off-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
Co-authored-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
…atchat#751)

* Updated README to include connecting to official backend

Signed-off-by: Jespercal <chap600@hotmail.com>

* Changed link for env.ts reference

Signed-off-by: Jespercal <chap600@hotmail.com>

---------

Signed-off-by: Jespercal <chap600@hotmail.com>
)

* chore: Update stoat.js dependency

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* chore: update ulid, run pnpm install

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

---------

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
* fix: hide alert if server is muted

Signed-off-by: meijerivoi <themeijerivoi@gmail.com>

* fix: change & to &&

Signed-off-by: meijerivoi <themeijerivoi@gmail.com>

* fix: Prettier line length change

Signed-off-by: meijerivoi <themeijerivoi@gmail.com>

---------

Signed-off-by: meijerivoi <themeijerivoi@gmail.com>
* fix: Make ProfileJoined.tsx follow locale

Fix: ProfileJoined.tsx hardcodes date format, make it adhere to locale instead

Basic change, it's also my first time really contributing. I think I understand how the locale system in the app works but I've had a rough time figuring it out so apologies if I still got something wrong.

I plan to have a go at fixing the rest of the issue but for now I'll just submit this.

Partially Fixes: stoatchat#811
Signed-off-by: Medic-Kronos <warmedic86@gmail.com>

* fix: Make ProfileJoined.tsx follow locale

Fix: ProfileJoined.tsx hardcodes date format, make it adhere to locale instead

Basic change, it's also my first time really contributing. I think I understand how the locale system in the app works but I've had a rough time figuring it out so apologies if I still got something wrong.

I plan to have a go at fixing the rest of the issue but for now I'll just submit this.

Partially Fixes: stoatchat#811
Signed-off-by: Medic-Kronos <warmedic86@gmail.com>

* fix: Quickly format it in the way it was before

Signed-off-by: Medic-Kronos <warmedic86@gmail.com>

---------

Signed-off-by: Medic-Kronos <warmedic86@gmail.com>
Co-authored-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
Signed-off-by: Amy <amy+git@amogus.cloud>
…hat#783)

* feat: Enable enhanced noise suppression with RNNoise

Imports livekit-rnnoise-process, this package requires an externally hosted rnnoise model.

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* feat: Translations for new enhanced noise suppression button

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: handle these buttons properly and add the getter for rnnoise

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Add "Powered by RNNoise" to the enhanced noise button, fix buttons
Buttons should now only turn off when the other turns on only.

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* style: Run linter on these files

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Remove translations, I was unaware of the automated extraction

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* chore: Update livekit-rnnoise-processor to v1.1.1

New version does not use webpack as a dependency and allows stubbing in a cdn url for the worklet

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Downgrade stoat.js to main version

This was accidentally commited.

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* feat: Allow rnnoise worklet cdn url to be configured with a vite var

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* refactor: Make suppression a ternary, and make the settings ui a select

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Typo for RNNoise with extra n

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* chore: Update livekit-rnnoise-processor to v2

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Forgot to save during the merge

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Broken lockfile

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: One more try on fixing the lockfil

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: remove unncessesary updates

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

---------

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
Co-authored-by: Mihai <45673304+mihaicm93@users.noreply.github.com>
feat: Accept autocomplete suggestion w/ tab, or accept emoji with a seocnd ':'
Addresses https://github.com/orgs/stoatchat/discussions/1418

Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com>
* meta: Added CODEOWNERS file

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>

* meta: Changed @infi to @stoatchat/ui-ux

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>

---------

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>
Co-authored-by: Thomas Ryan <contact@thomasryan.ca>
* meta: Added CODEOWNERS file

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>

* meta: Changed @infi to @stoatchat/ui-ux

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>

* meta: Added @stoatchat/for-web-maintainers to /packages/client

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>

---------

Signed-off-by: Thomas Ryan <contact@thomasryan.ca>
Co-authored-by: Thomas Ryan <contact@thomasryan.ca>
* feat: Copy username from profile modal/card

Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>

* chore: generate i18n catalog

Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>

---------

Signed-off-by: mihai <45673304+mihaicm93@users.noreply.github.com>
Co-authored-by: Stoat CI <stoat-ci@users.noreply.github.com>
ci: only check lingui in build & test ci: generate catalogs in release please workflow
ci: only run documentation workflow on main
ci: only run git town on non-fork PRs
ci: un-used branch trigger
bryanchriswhite and others added 27 commits April 14, 2026 20:43
lingui compile without --typescript generated CJS .js files, but the
project imports catalogs as ESM. Recompile with --typescript to produce
.ts files with `export const messages`.
Use the larger censer artwork as the splash screen visual instead of
the small app icon, with "Censer" text and loading spinner overlaid.
Increase splash image from 360px/60vw to 540px/90vw. Position "Censer"
text and spinner to overlap the bottom of the image, sitting in the
dark space below the censer artwork.
Bump default M3 contrast from 0.0 to 0.25 for better text/icon
legibility on dark surfaces. Replace hardcoded cool-tinted #0A0A0F
body/splash/PWA background with warm #19120c matching M3 dark surface.
Add marginBottom using --gap-md (8px) to CategorySection for visual
separation between channel groups like Internal, Start Here, Voice.
- Category collapse: hide all channels when collapsed (not just read
  ones), show unread dot indicator on category header when collapsed
  channels have unread messages
- Message backgrounds: use surface-container as default (was
  transparent), surface-container-high on hover for visible feedback
- Embed cards: use surface-container-high background with on-surface
  text for better readability (was primary-container which was too
  similar to primary title color)
- Voice channel sidebar: add explicit surface-container-lowest
  background to match text channel message area
- Voice channels: open messages pane by default when clicking a voice
  channel (was requiring manual toggle)
Add visual grouping for sequential messages from the same author by
flattening inner corner radii where bubbles meet.
- Add .env.e2e for Tilt/Kind dev stack E2E testing
- Implement LiveKit mocking layer for voice call testing
… overhaul

- Enable camera and screen-share buttons with error handling
- Camera flip and audio-only mode
- In-call mic/camera input selection and maximize/fullscreen controls
- Move chat to right panel with watch/stop-watching controls
- Spotlight voice tiles with auto-spotlight and manual pinning
- Hide members toggle in call actions
- Extensive layout fixes for spotlight, call cards, and video tiles
- Fix race conditions and null derefs in call UI
- Allow messaging non-friends with proper permission handling
- Text-first DM layout with dynamic voice switching
- Hide View Members icon and auto-close member sidebar for DMs
- SVG call icon, popover dismiss fixes, DM header label
- Add ADRs documenting DM UX decisions
- Update stoat.js submodule for DM permissions
Restores the CI config that was in the old branding branch but not
included in the clean feature/branding branch.
SolidJS render() appends to the target element rather than replacing
its contents, so the position:fixed splash div from index.html
persisted and covered the auth flow. Remove it after mount.
chat.episkopos.community is the CloudFront frontend distribution and
has no backend origin for /autumn, /api, /events, /january, /gifbox.
The IngressRoute matches stoat.episkopos.community — use that for all
backend service URLs so images, API, and WebSocket connections work.
handleTouchStart, handleTouchMove, and handleTouchEnd were referenced
in JSX but never defined, causing "ReferenceError: handleTouchEnd is
not defined" which crashed the entire component tree on render.

Implements long-press-to-drag (500ms) for mobile touch interactions.
The vite build OOMs on the default ~1.5GB heap due to the large bundle
(codemirror, prosemirror, shiki, livekit, fonts) with sourcemaps enabled.
The job-level `variables:` block with $VAR references was being
shadowed by the project-level AWS_ACCESS_KEY_ID variable, causing
the staging deploy to use production (stoat-ci) credentials instead
of staging (stoat-staging-ci) ones. Using `export` in the script
section ensures the staging keys take effect at runtime.
Add /oauth2/authorize route with full consent flow:
- Parse OAuth2 query params (client_id, redirect_uri, scope, etc.)
- Fetch app details from GET /oauth2/authorize API
- Show login form if user not authenticated
- Display consent screen with app name and requested scopes
- POST consent decision to backend
- Redirect back to third-party app with auth code

Also updated CI to use canonical censer.chat domains instead of
stoat.episkopos.community for staging deployment.
On mobile, the client lifecycle can stay in State.Ready longer, causing
the OAuth consent page to show an infinite spinner instead of the
authorize button.

Only show spinner during State.LoggingIn (active login), not State.Ready
(initial/idle state). The client quickly transitions Ready→Connecting→
Connected when restoring a session, so we don't need to wait for Ready.

Fixes mobile OAuth flow where users never see the authorize button.
Auth page footer:
- Replace GitHub/Bluesky/About/Terms/Privacy links with single GitLab link
- Link to self-hosted episkopos/community repo
- Remove Unsplash image attribution

Home page:
- Remove 'Discover Censer' card
- Remove 'Join the Censer Lounge' card
- Remove 'Donate to Censer' card
- Keep 'Create a group or server', 'Give feedback', and 'Open settings'

This removes all upstream Stoat/Revolt branding and external links,
replacing them with self-hosted episkopos community resources.
This card was missed in the previous branding removal commit.

Home page now only shows:
- Create a group or server
- Open settings
Reverts two upstream regressions that were cherry-picked into staging
but never landed on main. Brings the user-settings menu back into
alignment with the debranding methodology.

- Remove the Donate entry (introduced upstream by PR stoatchat#573, cherry-picked
  into staging as 7a42c98). It pointed to ko-fi.com/stoatchat, which
  is the upstream Stoat brand we are debranding away from. Our project
  is not donation-funded and has no equivalent destination.

- Restore the Source Code link to work.episkopos.community/episkopos/community
  (introduced upstream by PR stoatchat#622, cherry-picked into staging as 62ac91a,
  which had pointed it at github.com/stoatchat). The community repo on our
  self-hosted GitLab is the hub for all Censer-related repos (web, Flutter,
  backend) and is the correct destination.

Beads: stoat-frontend-cfc, stoat-frontend-w92
Tracked under chat-psem (Upstream Sync Automation System).
…h2 authorize

Bridge the Discord full-page redirect into the SPA using the backend's
one-time handoff code (Option A):

- Add a "Continue with Discord" button to the not-logged-in state of the
  OAuth2 authorize screen. It redirects to
  `/auth/discord/login?return=<current URL>`, carrying the OAuth2 authorize
  params so the round trip returns to this same consent screen.
- On mount, if `?discord_handoff=<code>` is present, POST it once to
  `/auth/discord/complete`. The code is stripped from the URL up-front
  (single-use server-side regardless).
  - Success: establish the session by reusing the exact tail of
    ClientController.login (state.auth.setSession + lifecycle
    LoginUncached transition), flipping isLoggedIn so consent renders.
  - MFA: reuse the client's existing `mfa_flow` modal + `/auth/session/login`
    ticket exchange to complete the challenge, then establish the session.
  - Error (expired/used/invalid): surface an error and fall back to the
    login form.

Session tokens are only ever received via the one-time complete exchange,
never read from a URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bryanchriswhite

Copy link
Copy Markdown
Author

Closing — this repo is GitLab-only per operator; re-opening the equivalent MR on GitLab.

@bryanchriswhite
bryanchriswhite deleted the feat/discord-login-handoff-web branch August 18, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.