chore(ci): push bunyip images to psa-systems-private + tag-driven builds #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "chore/bunyip-packages-to-psa-systems-private"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Brings bunyip's CI workflow in line with mokosh-server and mokosh-clients:
dev.a8n.run/${PSA_SYSTEMS_PRIVATE_PACKAGE_OWNER}/instead ofdev.a8n.run/psa-systems/get-tags.nuscripts for version-aware taggingv*tag pushes sogit tag v0.2.0 && git push --tagsproduces a properly tagged image🤖 Generated with Claude Code
WCAG-AA audit at 4.5:1 for body text, 3:1 for large text. Walked every authenticated and public route in both themes with a JS contrast scanner and fixed the genuine low-contrast spots: - dashboard SignedOutNotice: light-on-light headings (`text-bunyip-reed-900` / `text-bunyip-reed-700`) given dark variants - auth.rs reset-password helper text + verify-email NoToken state + magic-link sent state were missed by the first perl pass; ran perl again on auth.rs / landing.rs / dashboard.rs to catch the remaining `text-bunyip-reed-{600,700,800,900}` without dark variants - feedback page TAGS array: Bug / Feature / Flow / Idea idle + selected classes given full light/dark color pairs so chips stay legible in both modes - feedback page decorative blob in the top-right corner was a bright `bg-bunyip-water-100` blur that read as a spotlight in dark mode - dimmed to `dark:bg-bunyip-water-700/30` + `dark:opacity-40` - landing hero `PSA` highlight: bumped underline-pill colour to `water-700/60` and the text to `water-100` in dark so it has standalone contrast even without the bar Remaining auditor noise on the landing CTA strip (white text on `bg-gradient-to-br`) is a false positive - the scanner walks `background-color` only, so it can't see CSS gradients. Verified visually that both themes render correctly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Captures the per-page / per-route / per-API-module decisions from `docs/bunyip/01-merge-the-foundation.md`'s audit checklist so subsequent phases have a single source of truth. Lists which migrate-branch surfaces stay, which get rewired to mokosh-server endpoints, which get deleted (login-totp / magic-link / verify-email / feedback / admin-feedback), and which routes are net new (auth/callback, settings/{profile,security,sessions}, admin/{users,audit-logs}, signup/:token). Also lands `bunyip-web/bun.lock` so the tailwind CLI install is reproducible across machines. The lockfile was generated as part of running `bun run build:css` during the phase-01 build verification.Removes the same-origin /v1/... dev-proxy assumption from the API client. Every helper now hits the absolute mokosh-server URL configured at build time via `BUNYIP_OIDC_ISSUER` (`option_env!`). Bearer auth on authed calls; the access token lives in-memory in `stores::tokens::CURRENT_ACCESS_TOKEN` and is mirrored to localStorage under `bunyip.tokens` for refresh-on-reload. `credentials: 'include'` is intentionally absent: mokosh-server's CORS is `allow_origin(Any)` and incompatible with credentials. The Bearer-token-on-fetch model is what crosses origins. New SPA modules: - `stores/config.rs` - `OidcConfig::from_env()` reads BUNYIP_OIDC_{ISSUER, CLIENT_ID, REDIRECT_URI, SCOPES} at compile time, defaults SCOPES to `openid email offline_access`. - `stores/tokens.rs` - `Tokens` + `StoredTokens` + the in-memory access-token cell + localStorage save/load/clear under `bunyip.tokens`. API client surgery (`api/mod.rs`): - `request` is replaced by `base_builder` + typed `{get,post,put}_authed` helpers. The old `request("DELETE", ...)` shape is preserved as a backwards-compat shim so `api/orgs.rs` + `api/feedback.rs` still compile until they get rewired in phases 04 / 05. - Error envelope now decodes mokosh-server's `{ error, error_description }` shape (was the migrate-branch's `{ error: { code, message } }`). - `me::fetch_me` now calls `/v1/auth/me` and adapts the wire `MeView` into the SPA's existing `MeResponse` (memberships left empty, filled in by phase 04; `email_verified_at` synthesized as "verified" until /me grows the field). AuthContext (`stores/auth.rs`) hydrates the in-memory access token from localStorage on mount before the first /me fetch; clears tokens on 401 and collapses to SignedOut. Plumbing: - `Dioxus.toml` drops the dev `[[web.proxy]]` blocks (no longer needed; the SPA hits the absolute issuer URL). - `.env.example` documents the four new BUNYIP_OIDC_* knobs. - `compose.dev-sso.yml` (new) puts bunyip-web behind Traefik at `${USER}-bunyip.a8n.run` and surfaces the OIDC env vars to the build environment. `just dev-sso` is the entry point. - mokosh-server's `justfile` gains a `register-bunyip-client` recipe that mirrors the existing `register-client` recipe but targets bunyip-web's host and prints the UUID for `bunyip/.env`. cargo check passes on both targets (`just check-compile`, `just check-web`).bunyip-web now owns the auth UX, wired to mokosh-server's `/v1/auth/*` and `/oauth2/*` surfaces. Visual design (AuthShell + AuthInput + SubmitButton) preserved from the migrate branch; functional pages rewritten. New SPA modules: `modules/oidc/` (mod.rs, flow.rs, pkce.rs, storage.rs, tokens.rs). Lifted from mokosh-clients's `modules/oidc/` with the BUNYIP_OIDC_* env prefix substituted and `current_access_token` sourced from `crate::stores::tokens`. `Tokens` + `StoredTokens` re-exported from `stores::tokens` so the rest of the SPA can read them without pulling the OIDC module in. PKCE storage key is `bunyip_oidc_flow_v1`. LoginPage: single component state machine. Password form -> on success store tokens + nav to Dashboard / return_to; on `mfa_required` flip to MFA prompt view with auto-submit on the 6th all-digit char, Trust-this-browser checkbox, friendly retry errors ("That code didn't match. Try again.", "Your code prompt expired", "Too many attempts"). Reads `?return_to=` from the URL with a strict allowlist (must look like an OIDC authorize query) and round-trips it via `window.location.set_href` after a successful sign-in for the cross-app SSO bridge (phase 07). SignupPage: emits `POST /v1/auth/signup { email }`, shows the enumeration-resistant "we've sent a link" copy. SignupCompletePage (`/signup/:token`): previews via `GET /v1/auth/signup/by-token/:token`, then `POST .../complete` with `{ password, first_name, last_name }`. ForgotPasswordPage: `POST /v1/auth/password-reset { email }` + the same "we've sent a link" copy. ResetPasswordPage (`/reset-password/:token`): previews, then `POST .../complete` with password + confirmation. AuthCallbackPage (`/auth/callback`): consumes the OIDC code via `oidc::complete_login`, persists tokens, refreshes AuthContext, navigates to return_to or Dashboard. Deleted as flows but kept as one-line redirect stubs to /login: `LoginTotpPage` (merged into LoginPage's MFA view), `MagicLinkPage`, `VerifyEmailPage`. The Route enum entries stay so bookmarks do not 404; the redirect lands users in the right place. api/auth.rs rewritten to call mokosh-server: signup_start / signup_preview / signup_complete / forgot_password / reset_preview / reset_complete / logout. The old `login` / `signup` / `totp_verify` / `magic_link_request` helpers are gone; password_login + mfa_verify live in `modules/oidc/flow.rs` because they produce `Tokens` bundles that the AuthContext consumes. Plumbing: `main.rs` calls `modules::oidc::snapshot_initial_search()` before `dioxus::launch` so Dioxus's router cannot erase the OIDC callback query string before AuthCallbackPage reads it. `components/layout.rs::AuthShell` now takes `title: String, subtitle: String` (was `&'static str`) so the SignupComplete / ResetPassword pages can fill in the previewed email. New Cargo deps: base64, sha2 (workspace), rand 0.9 (overridden from workspace 0.8 to match the getrandom 0.3 wasm story), js-sys, thiserror. `just check-web` is green.Adopts the same recipe set + structure mokosh-clients uses, adapted for bunyip's workspace (api native + web wasm + mocks): - `install-hooks` writes a `.git/hooks/pre-commit` stub that execs `just pre-commit`. Bypass with `git commit --no-verify`. - `pre-commit` runs the same checks as the Forgejo `check.yml` job inside the rust-builder-glibc image: fmt, clippy (workspace), native check (workspace minus bunyip-web), wasm check (bunyip-web only), test (workspace minus bunyip-web). - `ensure-npm` + `css-build` + `css-watch` for the Tailwind step. Bunyip's `package.json` lives in `bunyip-web/`, so the recipes `cd` into it. - `dev` now uses `with-env` to inject `BUNYIP_HOST_BIND_IP` / `HOST_UID` / `HOST_GID` / `USER` into the compose run rather than rewriting `.env` on every invocation. Same end result; cleaner state. - `dev-sso` likewise uses `with-env` plus a defensive `docker network create` for the external `dev-bunyip-private-${USER}` network the base compose declares. - Unified `down` recipe stops both LAN-IP and SSO modes regardless of which `just dev*` was used to start them, with `--remove-orphans` for cleanup of the previous mode's containers. - `dev-down` / `dev-sso-down` stay as targeted equivalents. - All existing bunyip-specific recipes preserved (check-compile, check-web, check-clippy, check-fmt, fmt, test, build, build-web, check-seeds, check-docker-api, check-docker-web, build-docker, create-release). cargo-target / cargo-registry volumes are now `dev-bunyip-cargo-target` / `dev-bunyip-cargo-registry` (matching the existing per-user `dev-bunyip-cargo-target-${USER}` volume from compose.dev.yml, just sans the per-developer suffix for the CI-style hook image).restartrecipe 294d372052If the user has a still-valid refresh chain (use_auth_provider just exchanged it for a fresh access token) and they hit /login - typically because some other first-party RP bounced them here via the SSO bridge - they should NOT see the form. They should just complete what they were trying to do. Added a use_effect on LoginPage that fires when auth flips to SignedIn: - If the URL carries `?return_to=` (SSO bridge), top-level navigate to ${issuer}/oauth2/authorize?<return_to> so mokosh-server can mint a code for the originating RP. The OP cookie is already set (it survives across origins under the .a8n.run cookie domain), so authorize closes the loop without prompting. - Otherwise nav.replace to /dashboard so a direct visit to /login while signed-in doesn't trap the user on a form. Combined with the background refresh loop, the result is: a user who signed in to bunyip yesterday and goes straight to mokosh-clients today never sees a sign-in form - the OP cookie + refresh chain handle everything. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: YousifShkara <yousif@niceguyit.biz>Bundles the seven post-demo follow-ups so the SaaS shell feels stable before more flows land on top. Each block below is independent in spirit; commits were merged into one because most affect overlapping files in components/layout.rs and pages/dashboard.rs. 1. Sign-out unified through `/logout` (pages/logout.rs is the only handler; layout.rs AppShell and pages/dashboard.rs DashboardHeader now `nav.replace(LogoutPage)` instead of duplicating teardown). Adds `web_sys::console::log_1` traces in logout.rs so a stuck sign-out is visible in DevTools without a rebuild. pages/sessions.rs current-session revoke also routes through `/logout` instead of `window.location.replace`, so the auth signal moves to SignedOut deterministically rather than relying on a hard reload. 2. Reactive password-strength checklist on signup-by-token and reset-password (components/password_checklist.rs, mounted in pages/auth.rs SignupCompletePage + ResetPasswordPage). Rules mirror `mokosh_auth_core::policy::validate_password_strength` exactly. Submit stays disabled until every rule is green. api/mod.rs now parses `{ "error": ..., "details": { "password": ... } }` and surfaces the field-level reason via the existing InlineError slot, so the user sees "password must be at least 12 characters" instead of a bare 400. 3. SafeImage component (components/image.rs) - thin <img> wrapper with onerror fallback to a children slot. Empty/whitespace src renders the fallback synchronously (no failed network request). Wired into pages/dashboard.rs AppTile so OIDC client icon 404s fall back to the app's first-letter glyph instead of Chrome's broken-image icon. 4. AppShell now accepts an optional `back_to: Option<Route>` plus `back_label`, rendering a "← Back to {label}" link in the page header. Wired across all nested settings/admin pages: profile, security, sessions, active_tenant, orgs (list + members + billing), invite_create/list, audit_logs, user_management, admin/feedback. Inline `Link { to: ... "← Back to ..." }` rows in orgs/billing/invite_create/invite_list removed in favour of the shared AppShell-owned control. Browser back still works because the back link is a real router Link. 5. Brand image centering. BrandMark SVG gets `dark:text-bunyip-reed-200` so the reed icon stays visible on the dark AuthShell/AppShell. BunyipMascot face shifted up ~30 units in the 400x400 viewBox so the eyes sit near the geometric center instead of crowding the bottom third; ripples, head, eyes and tagline all moved together so the composition still reads. Tagline pill switched from `-bottom-2 right-2` to `-bottom-2 left-1/2 -translate-x-1/2` so the badge actually sits on the disc's centerline. 6. Uniform grid-of-cards. Three card grids equalised: HubSection (settings.rs), AppTile grid (dashboard.rs), FeatureCard grid (landing.rs). Each grid gets `auto-rows-fr`, each card gets `h-full flex flex-col` so the tallest card sets the row height and CTAs (`Open →`, `Launch →`) pin to the bottom via `mt-auto` regardless of description length. 7. Dead-button audit. AppHeader (components/layout.rs) was unused dead code that contained three onclick-less buttons - removed entirely. orgs.rs "+ New org" stubbed with `disabled + title="Coming soon - org creation API not wired yet"`. pricing.rs tier CTAs converted from no-op buttons to `Link` to SignupPage (matches landing-page CTA pattern). Calendar prev/next/today wired client-side via chrono (mokosh-clients side); event-create / Week / Day toggles stubbed with `disabled + title`. Time-sheet prev/next-week wired similarly. Notifications bell in mokosh-clients top-bar stubbed pending the notifications API. Verified: `cargo check --package bunyip-web --target wasm32-unknown-unknown` clean (28 pre-existing warnings unchanged); `cargo fmt --all` applied. Browser walk-through pending the next `just dev-sso` boot.Implements docs/bunyip-settings/04-ui.md against the new mokosh-server endpoints from the matching 03-server-additions.md. api/admin.rs: - UserListFilter (search/role/status/mfa/limit/offset) with to_query() URL encoder. - UserListPage envelope ({users, total, limit, offset}); list_users() now takes a &UserListFilter and returns the envelope. - UserDetail envelope ({user, available_role_transitions}); new get_user(id) helper. - New mutation helpers: change_user_role, delete_user, resend_verify, admin_trigger_password_reset. pages/user_management.rs (rewritten): - Filter row: search input + role/status/mfa dropdowns + Reset button. Every change resets offset to 0. - Pagination strip at the bottom of the table with prev/next; "Showing N-M of TOTAL" readout. - Each row's name + email is now a Link to /admin/users/:id. - Inline suspend/reactivate + force-disenroll buttons preserved for one-click admin tasks. pages/user_detail.rs (new, ~700 lines): - Five sections: Profile (read-only header w/ status + role badges), Role + status (legal-only role buttons computed from server's available_role_transitions + suspend/reactivate), Security (force-disenroll MFA, resend verification when pending, admin-trigger password reset when verified), Audit cross-link, Danger zone (delete user). - ChangeRoleModal: confirm with optional reason; if promoting to admin, embeds StepUpVerifyForm and disables Save until a step_up_token is obtained. - DisenrollMfaConfirm: reason-gated. - DeleteUserConfirm: requires typed-email match AND step-up token AND reason before enabling the final Delete button. - StepUpVerifyForm: POSTs /v1/auth/mfa/step-up/start to issue a challenge, then /verify with the admin's TOTP code; surfaces the resulting step_up_token to the parent modal. routes.rs + pages/mod.rs: wire /admin/users/:user_id (AFTER the literal /invite + /invites subpaths so dioxus picks them first). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: YousifShkara <yousif@niceguyit.biz>UI side of doc 03. Pairs with mokosh-server commits 0708bad / 8346c9e / 97cef49 (schema + Pg impls + 8 /v1/orgs handlers). - `+ New org` button on `/settings/orgs` now opens a `CreateOrgModal` that POSTs `/v1/orgs` with `{name, slug?}` (slug optional; server auto-derives from name). On success: toast, refresh the list, navigate to the new org's members page so the founder lands ready to invite. - New `components/org_switcher.rs::OrgSwitcher` replaces the static `[● Personal | User]` pill in both `AppShell` (settings + admin pages) and the dashboard header. Click the pill -> dropdown of `/v1/auth/memberships`. Picking another tenant POSTs `/v1/auth/active-tenant` to reissue the token bundle, then reloads so every downstream resource refetches under the new scope. Dropdown footer links to `/settings/orgs`. - `api/orgs.rs`: new `CreateOrgRequest` + `create_org()` helper. `just check-web` is green.Server returns {"error": "not_enrolled"} when the calling admin doesn't have MFA enrolled, but the raw body was being displayed verbatim ('step-up start: network: {error: not_enrolled}'). Map to: 'You need to enable MFA on your own account before performing this action. Set it up under Settings -> Security.' Also translates rate_limited to a tighter message. Other failures fall through to the prior 'Could not start step-up: <e>' format.Brings bunyip's CI into line with mokosh-server and mokosh-clients: - Pushes to dev.a8n.run/${PSA_SYSTEMS_PRIVATE_PACKAGE_OWNER}/ (was dev.a8n.run/psa-systems/) - Uses the per-image get-tags.nu scripts for version-aware tagging - Triggers on v* tag pushes so `git tag v0.1.0 && git push --tags` produces a versioned image - Adopts buildx with container-driver builder and registry-backed cache Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>