feat: M10 — native SSO fixes + staff operations #21

Merged
whitlocktech merged 6 commits from feat/m10-native-sso-fix into main 2026-07-21 21:38:49 +00:00
Member

Fixes the four issues found in on-device QA and adds the staff-operations surface. Consumes existing backend routes — no backend changes (the companion website PR is dev-only tooling). Verified end-to-end on an emulator against the dev backend.

SSO (native, no browser fallback)

  • Discovery is now legible. AuthRepository.ssoProviders() returns Available / None / Unavailable (retries once). The login screen renders native SSO, a loading hint, or a retry — the old dead website-login hand-off (which couldn't deep-link a mobile session back, so it hung) is removed.
  • Single "Sign in with SSO" button → native provider picker (a ModalBottomSheet built from discovery; straight through when there's one provider). No website chooser page, no Google SDK.
  • Survives Custom-Tab process death: the pending PKCE {state, verifier} is persisted via a new encrypted PendingSsoStore.
  • Login auto-dismiss now keys off the shared session, robust to the deep-link/recomposition timing of the Custom-Tab return.

Notifications

  • The nav drawer is now verticalScroll-wrapped so a signed-in session's longer menu (which includes Notifications) can't clip on short screens. (The entry/screen already existed on main; the reported "missing" was an older build predating the feature.)

Staff operations (§1, §6.4)

New STAFF/ADMIN menu access levels + a StaffGate; the backend re-checks role on every /api/v1/admin/** call. Screens over existing routes:

  • Dashboard — counts, recent admin activity, admin-only site-mode (live/maintenance) toggle.
  • Content — news posts (list/create/publish/delete) + wiki categories/tags (list/create/delete). CMS block/hero editor stays out of scope.
  • Moderation — kick / ban / unban / broadcast.
  • Support queue — open help pages: reply (optionally closing) / close.

Moderation + support need a live sidecar; offline they degrade cleanly (clear error on writes, empty queue on the list).

Verification (emulator)

Native SSO picker → IdP → deep link → exchange 200 → signed in → returns Home; Dashboard site-mode flips /public/status maintenance↔live; Content publish/create/delete confirmed in MariaDB; staff entries show for staff only. Builds, lint, and unit tests green (JDK 17 on CI).

Companions


AI disclosure (org policy): authored with Claude Code (Claude Opus); AI-authored commits carry a Co-Authored-By: Claude trailer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr

Fixes the four issues found in on-device QA and adds the staff-operations surface. Consumes existing backend routes — **no backend changes** (the companion website PR is dev-only tooling). Verified end-to-end on an emulator against the dev backend. ## SSO (native, no browser fallback) - **Discovery is now legible.** `AuthRepository.ssoProviders()` returns `Available` / `None` / `Unavailable` (retries once). The login screen renders native SSO, a loading hint, or a retry — the old dead website-login hand-off (which couldn't deep-link a mobile session back, so it hung) is **removed**. - **Single "Sign in with SSO" button → native provider picker** (a `ModalBottomSheet` built from discovery; straight through when there's one provider). No website chooser page, no Google SDK. - **Survives Custom-Tab process death:** the pending PKCE `{state, verifier}` is persisted via a new encrypted `PendingSsoStore`. - **Login auto-dismiss** now keys off the shared session, robust to the deep-link/recomposition timing of the Custom-Tab return. ## Notifications - The nav drawer is now `verticalScroll`-wrapped so a signed-in session's longer menu (which includes **Notifications**) can't clip on short screens. (The entry/screen already existed on `main`; the reported "missing" was an older build predating the feature.) ## Staff operations (§1, §6.4) New `STAFF`/`ADMIN` menu access levels + a `StaffGate`; the backend re-checks role on every `/api/v1/admin/**` call. Screens over existing routes: - **Dashboard** — counts, recent admin activity, admin-only site-mode (live/maintenance) toggle. - **Content** — news posts (list/create/publish/delete) + wiki categories/tags (list/create/delete). CMS block/hero editor stays out of scope. - **Moderation** — kick / ban / unban / broadcast. - **Support queue** — open help pages: reply (optionally closing) / close. Moderation + support need a live sidecar; offline they degrade cleanly (clear error on writes, empty queue on the list). ## Verification (emulator) Native SSO picker → IdP → deep link → `exchange` 200 → signed in → returns Home; Dashboard site-mode flips `/public/status` maintenance↔live; Content publish/create/delete confirmed in MariaDB; staff entries show for staff only. Builds, lint, and unit tests green (JDK 17 on CI). ## Companions - Backend scope/docs: `RunicGateway/docs` #30 - Dev SSO tooling: `RunicGateway/website` #91 --- **AI disclosure (org policy):** authored with Claude Code (Claude Opus); AI-authored commits carry a `Co-Authored-By: Claude` trailer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
wtclaude added 6 commits 2026-07-21 21:32:33 +00:00
On-device, the native SSO buttons never appeared and the flow dumped users on
the desktop website login (which can't deep-link a mobile session back), so it
hung. Two app-side causes:

1. Discovery conflated "no providers" with "call failed" (ssoProviders() returned
   emptyList() on any error) and the screen then showed a dead website-login
   hand-off. Now ssoProviders() returns Available/None/Unavailable, retries once,
   and the login screen renders native provider buttons, a loading hint, or a
   retry — never the website login fallback (removed, along with WebsiteUrls.login).

2. The pending {state, verifier} lived only in memory, so a Custom-Tab-induced
   process eviction lost it and the exchange failed STATE_MISMATCH. Persist it via
   a new encrypted PendingSsoStore (EncryptedSharedPreferences, mirrors the token
   store), cleared the moment the callback is consumed so replays still fail closed.

SsoAuthManager stays framework-free (store behind an interface). +1 test proving a
fresh manager on the persisted store completes (process-death sim); 15/15 SSO tests
pass, lint + assembleDebug green (JDK21, -Pksp.incremental=false).

Verified end-to-end against the local site via the dev stub IdP: player and admin
both sign in natively and receive the correct role.

Co-Authored-By: Claude <noreply@anthropic.com>
The ModalDrawerSheet stacked all items in a non-scrolling column. A signed-in
session adds My account, Notifications, and the three player groups (11 nav items
+ sign-out + change-server), which overflows the drawer height on shorter screens
or larger display-size / font-scale settings — clipping the lower entries
(Notifications among them) so they can't be reached. Wrap the drawer content in a
verticalScroll column so every entry is reachable regardless of screen height.

Verified on-device: a signed-in player sees Home…My houses + Sign out + Change
server, with Notifications present and its screen reachable.

Co-Authored-By: Claude <noreply@anthropic.com>
Add the staff-operations surface scaffolding and the first group. Session gains
isStaff/isModerator/isAdmin; the menu gains STAFF (admin/editor/moderator) and
MODERATOR (admin/moderator) access levels, plus a StaffGate mirroring PlayerGate.

Dashboard group (over the existing /api/v1/admin, bearer-authed, role re-checked
every request): AdminApi/AdminDto/AdminRepository for GET /admin/dashboard and
PUT /admin/site-mode; AdminDashboardScreen shows site mode, summary counts, and
recent admin activity, with an admin-only maintenance/live toggle.

Verified on emulator against the dev backend: an admin sees the Dashboard entry
(a player does not); counts + audit log render from real data; the site-mode
toggle flips /public/status to maintenance and back to live. MenuAccessTest +2
(8 total), assembleDebug + lint green.

Co-Authored-By: Claude <noreply@anthropic.com>
Second staff group over the existing /admin routes (any staff role; bearer-authed,
role re-checked every request). AdminApi/AdminDto/AdminRepository gain posts
(list/create/publish-toggle/delete) and wiki taxonomy (list categories + tags,
create/delete category). AdminContentScreen is a two-tab screen (Posts | Wiki) with
create dialogs; the CMS block/hero editor stays out of scope. Admin wiki DTOs are
prefixed (AdminWikiCategoryDto/AdminWikiTagDto) to avoid colliding with the public
wiki DTOs.

Verified on emulator against the dev backend: posts list with published/draft pills;
publish/unpublish flips the DB row with live reload; create a news post; create +
delete a wiki category (confirmed in MariaDB). assembleDebug + lint green.

Co-Authored-By: Claude <noreply@anthropic.com>
The final two staff groups, both admin/moderator (MODERATOR menu access; StaffGate
now takes a role predicate). Over the shard write plane `/admin/shard/*`:
- Moderation: kick / ban / unban an account + broadcast a system message
  (AdminModerationScreen form + AdminModerationViewModel guarded actions).
- Support queue: list open help pages, reply (optionally closing), close
  (AdminSupportScreen + AdminSupportViewModel).

These need a live sidecar; offline they degrade cleanly (a clear error on writes,
an empty queue on the list) — never a crash (§7). AdminApi/AdminDto/AdminRepository
extended with the shard-op + help-page endpoints.

Verified on emulator: both entries appear for an admin (drawer now scrolls through
all four staff items); moderation broadcast returns a clean failure with the shard
offline; the support queue shows its empty state. assembleDebug + lint green.

Co-Authored-By: Claude <noreply@anthropic.com>
feat(sso): single "Sign in with SSO" button with a native provider picker
All checks were successful
PR Checks / android-build (pull_request) Successful in 6m10s
Release APK / release (push) Successful in 9m26s
422892f1ed
Collapse the per-provider login buttons into one "Sign in with SSO" entry. With a
single configured provider it launches straight through; with several it opens a
native ModalBottomSheet picker (driven by the discovery list the app already
fetches — no website chooser page, no Google SDK). Each row opens the Custom-Tab
bridge for that provider.

Also make the login screen dismiss reliably after any sign-in: the LOGIN
destination now pops as soon as the shared session becomes SignedIn, not only via
the login VM's local flag — the deep-link/recomposition timing of the Custom-Tab
return could otherwise leave the login screen up even though the session was
established.

Verified on emulator with two providers: the picker lists both, completing SSO via
one signs in and returns to Home (exchange 200, session persisted). lint + build green.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech approved these changes 2026-07-21 21:34:42 +00:00
whitlocktech scheduled this pull request to auto merge when all checks succeed 2026-07-21 21:34:47 +00:00
whitlocktech merged commit d6d966882b into main 2026-07-21 21:38:49 +00:00
whitlocktech deleted branch feat/m10-native-sso-fix 2026-07-21 21:38:50 +00:00
Sign in to join this conversation.
No description provided.