mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* feat(web): require Spaces login + at least one Space to access the app Gate every non-public route behind SIWE login and at least one Space, behind a new chain feature flag REQUIRE_LOGIN_DISABLED (default OFF = gate ON). Read the flag from chain 1 so the answer is stable on routes with no active chain context. Bounce unauthenticated users to /welcome/spaces?next=<original-url> and preserve next through the full onboarding flow (create-space -> select-safes -> invite-members) so they land on the URL they originally tried to open. Sanitise next to reject protocol-relative / absolute URLs. / redirects to /welcome/spaces when the gate is on, and /spaces without a spaceId redirects to /welcome/spaces. Hide the Accounts/Spaces tabs (AccountsNavigation) and the Topbar on /welcome/spaces while the gate is on so the page is a clean login/Spaces list. Adding /welcome/spaces to NO_HEADER_ROUTES also fixes a hydration error when ?safe= is in the URL (Topbar hooks read different values SSR vs client). Also fix a pre-existing race in WalletProvider where the initial wallet state could stay null if connectLastWallet populated onboard.state before the subscription started. Always-public routes (terms, privacy, cookie, licenses, imprint, safe-labs-terms, 403/404/_offline, /share/*, OIDC callback) remain reachable without a Space. * fix(web): revert WalletProvider change; gracefully render select-safes without a wallet Per review feedback, do not modify the global WalletProvider for this feature. Instead, restructure /welcome/select-safes so the form always renders: the connect-wallet prompt is now a small inline element inside the otherwise-empty Safes list area rather than a full screen takeover. Continue/Skip remain primary actions regardless of wallet state, so a brief useWallet=null window during onboard restore no longer flashes "Connect your wallet to see your safes" at the user. * fix(web): don't store `/` as the `next` redirect target Visiting `/` while logged out was producing /welcome/spaces?next=%2F, which is a no-op round-trip — `/` itself just redirects back to /welcome/spaces. Reject bare `/` in sanitizeNextUrl and apply the sanitiser to currentUrl too, so the guard simply lands on /welcome/spaces without a meaningless next= param. * feat(web): auto-trigger SIWE after wallet connect on the Spaces sign-in button After the user clicks "Connect wallet" on the Spaces sign-in flow, fire the SIWE prompt as soon as the wallet shows up in context, instead of making the user click a second "Continue with wallet" button. Skip the auto-trigger if the user is already SIWE-authenticated — that's just a wallet switch and re-signing would be noise. * fix(web): redirect /welcome to /welcome/spaces and reject self-redirecting paths as `next` When the require-login gate is on, the legacy /welcome page now redirects to /welcome/spaces — both because it shouldn't render the old "Get started" NewSafe UI in this flow, and because the route guard would otherwise stash "/welcome" as `next` and bounce the user back to the same redirector. Sanitise also rejects /welcome and /welcome/spaces (in addition to / from the previous commit) so manually crafted next= values can't loop either. * fix(web): redirect from /home (and other protected routes) before wallet is ready The route guard's first rule allowed the page to render whenever `isWalletReady` was false, which meant unauthenticated users landing on /home would briefly see the dashboard (and trigger downstream hydration issues in NewsCarousel) before the gate-on rules ever got a chance to fire. The gate rules only need the Redux store to be hydrated (so isSiweAuthenticated is trustworthy) — they don't actually need the wallet system. Swap the early-allow trigger to `!isStoreHydrated` so the auth-based redirects fire as soon as we know who the user is, even if onboard is still bootstrapping. * fix(web): stop /home ↔ /welcome/spaces redirect loop and drop redundant safe= Two things were combining to ping-pong unauthenticated users between /home and /welcome/spaces: 1. The login-redirect target appended ?safe= alongside ?next=, even though the safe was already inside the encoded next= URL. Drop the duplicate. 2. The /welcome/spaces page calls useFeatureFlagRedirect, which pushes to /welcome/accounts whenever the SPACES chain feature is off. With the gate on, the route guard then bounces /welcome/accounts back to /welcome/spaces — instant loop. Skip the redirect when the gate is on; /welcome/spaces is the canonical login page in that mode and doesn't need to defer to /welcome/accounts. The onboarding redirect (/welcome/create-space) is cleaned up the same way — no duplicate safe= on the target. * fix(web): address PR review feedback on the require-login gate 1. Don't coerce isRequireLoginEnabled === undefined to false in the route guard. Until the chains config resolves the flag we now allow the page to render instead of running the legacy redirect rules — those would otherwise misroute logged-out deep links to /welcome and drop the intended next= round-trip. 2. /spaces (no spaceId) → /welcome/spaces now preserves router.query so safe= / chain= / tracking params survive the hop instead of being silently dropped. 3. Unauthenticated users on onboarding routes (e.g. /welcome/create-space) now redirect to /welcome/spaces?next=<onboarding-route> instead of falling through to the legacy "not signed in" rule, which lands on /welcome and loses the round-trip target. * fix(web): make /welcome/spaces topbar hiding conditional on the gate NO_HEADER_ROUTES previously hid the Topbar on /welcome/spaces unconditionally, but when the require-login gate is OFF that page is the legacy Spaces list and the Topbar should still appear there. Hide it only when useIsRequireLoginEnabled() === true; while the flag is loading (undefined) we leave the Topbar in place so we don't change the visible chrome before knowing the user's mode. * fix(web): bypass require-login gate under Cypress (IS_TEST_E2E) The existing Cypress smoke & regression suites rely on visiting Safe URLs directly without going through SIWE login. Force the gate OFF when IS_TEST_E2E is true so those flows keep working without each test having to learn about the new redirect. Same pattern is already used for the Safe Labs terms bypass. * fix(ci): plumb a NEXT_PUBLIC_ flag for Cypress so IS_TEST_E2E survives the build next build forces process.env.NODE_ENV to 'production' in client bundles, which means the existing IS_TEST_E2E (derived from APP_ENV === 'cypress') is always false at runtime in production-built Cypress runs. The useSafeLabsTerms bypass happened to work via a parallel !IS_PRODUCTION check, but our require-login gate has no such side-channel. Add NEXT_PUBLIC_IS_TEST_E2E so the flag survives the static export, set it to 'true' in the Cypress GitHub Action, and OR it into IS_TEST_E2E. The require-login gate now correctly bypasses for the Cypress smoke and regression suites. * feat(web): classic-view escape hatch behind CLASSIC_VIEW_DISABLED flag (#7889) * feat(web): add classic-view escape hatch behind CLASSIC_VIEW_DISABLED flag Logged-out users on /welcome/spaces get a "View classic app" link that opts them out of the require-login gate for the rest of the tab session. The link honours the ?next= deep link that brought them to the gate (e.g. /home?safe=…) and falls back to /welcome/accounts when none is present. Closing the tab returns to the gated experience — the opt-in is sessionStorage only, not persisted across tabs. A non-dismissable banner is mounted at the top of PageLayout while the user is in classic view and not signed in, warning that the view is deprecated and linking back to /welcome/spaces (clicking the link clears the opt-in). The whole escape hatch sits behind the inverted CLASSIC_VIEW_DISABLED chain flag (default OFF = available). When the flag is explicitly enabled the link is hidden and the banner stops rendering, but existing session opt-ins are still honoured for the rest of the tab so we don't yank users mid-flow. The opt-in itself short-circuits useIsRequireLoginEnabled even while the chains config is still loading, so the gate doesn't briefly flicker on after an explicit bypass. * refactor(web): turn classic-view deprecation banner into a one-shot toast Replaces the sticky top-of-page ClassicViewBanner with a renderless ClassicViewToast that dispatches a warning notification once when a signed-out user is opted into classic view, and dismisses it via closeByGroupKey when they sign in or opt out. Message text and the "Log in" link (disable opt-in + navigate to /welcome/spaces) are preserved; only the presentation moves from a persistent banner to the shared notifications surface. * fix(web): re-engage require-login gate when an opted-in user signs in The classic-view opt-in's docstring promised the override held only "until the tab is closed or they sign in", but the hook short-circuited on the opt-in regardless of auth state, so a user who opted into classic view and then signed in within the same tab would keep the gate OFF for the rest of the session — silently bypassing the post-login "no Spaces → onboarding" redirect in useFlowActivationGuard. Gate the short-circuit on !isAuthenticated so the opt-in is honoured only while signed out, and add a unit test covering the signed-in case. --------- * feat(web): show local Safes count on spaces sign-in screen (#7905) * chore: drop pipefail from commit-msg hook for Linux VM compatibility Mirrors the fix applied to .husky/pre-commit in #7890. The hook's shebang asks for bash but it runs under dash on some Linux VMs, which rejects 'set -o pipefail'. Validation logic is unchanged. * feat(web): show local Safes count on the spaces sign-in screen Detect locally persisted Safes (addedSafesSlice) and surface an outlined green block above the sign-in card on /welcome/spaces. Hidden when no Safes are stored in this browser. --------- * tests(web): mock chain-dependent hooks in SpacesList test SpacesList now calls useIsRequireLoginEnabled, which reaches RTK Query via useChain and needs a Redux Provider the test doesn't supply. Mock useIsRequireLoginEnabled and useIsClassicViewFeatureEnabled (used by the SignedOutState branch) so the bare render keeps working. * feat(web): move local Safes alert into the sign-in card Place the "X Safes detected" alert under the "Sign in to view or create a Space." description, inside the same Card, so it reads as part of the sign-in prompt rather than a separate banner above it. * fix(web): align local Safes alert width with sign-in buttons * fix(web): skip /welcome/accounts hop on sidebar logo under login gate Sidebar logo's href was computed from pathname alone, so signed-out users clicking it would land on /welcome/accounts and then be bounced by the route guard to /welcome/spaces. Make the href gate-aware so it goes straight to /welcome/spaces (the canonical landing page when the require-login gate is on), and keep the legacy toggle when the gate is off. * fix(web): defer require-login redirects until the flag resolves Three callers were coercing `useIsRequireLoginEnabled()` `undefined → false`, which let their effects fire during the ~100ms window before the chains config loaded: - useFeatureFlagRedirect: could ping-pong to /welcome/accounts on first mount when SPACES=false and the gate flag was still loading, only to be bounced straight back by the route guard once the gate resolved to ON. - useSignInRedirect: a fast SIWE sign-in against a slow chains response could leave the user stranded on /welcome/spaces because the ?next= redirect branch only ran when the gate was already known to be on. - pages/welcome: rendered the legacy NewSafe screen for one paint before redirecting to /welcome/spaces, flashing UI the user shouldn't see. All three now treat undefined as "wait" instead of "off" and only act on the explicit boolean result. Added tests covering the loading state for each consumer. * fix(web): use timeless wording in classic-view deprecation toast The hardcoded "deprecated in 1 month" copy would silently become inaccurate as time passes. Reword to "deprecated soon" so the message stays correct without code changes, and align with the Spaces → Workspaces rename ("create a workspace"). The existing test asserts against the exported constant, so the snapshot keeps tracking the live copy. * fix(web): parse next URL into a structured router object Three new sites in this PR (sign-in redirect, classic-view escape hatch, invite-members onboarding) pass `sanitizeNextUrl(query.next)` straight into `router.push()`. CodeQL flags these as js/client-side-unvalidated-url-redirection because the taint analyser doesn't follow the sanitizer call. Add a `parseNextUrlForRouter` helper that runs the same sanitiser then reconstructs the URL into `{ pathname, query, hash? }` via `new URL()`. Routing through structured router objects is the pattern CodeQL recognises as safe, and the explicit URL parsing is a stronger sanitisation contract than passing the raw string. Existing call sites are updated to consume the structured form; the old `sanitizeNextUrl` helper is kept for callers that genuinely need the string (e.g. round-tripping `next=` through another URL). * fix(web): blank protected UI + unsubscribe Safe hooks when gate is blocking Reported bug: a user with a connected wallet (no SIWE session) opening a bookmarked /home would see Dashboard flash for ~1s before being bounced to /welcome/spaces, and toasts about pending txs / messages would pop during the redirect and persist on the login page. Two fixes share a new useIsAuthGateBlocking() selector (true when the gate flag !== false AND the user is not signed in): 1. PageLayout returns null for protected pathnames while the gate is blocking, so page data hooks never mount. /welcome/spaces, onboarding routes, and always-public routes (legal, /share/*, /404) still render. 2. InitApp moves Safe-scoped subscription hooks (useTxNotifications, useSafeMessageNotifications, useSafeNotifications, useTxPendingStatuses, useSafeMessagePendingStatuses, usePortfolioRefetchOnTxHistory, useTxTracking, useSafeMsgTracking, useCounterfactualSafeSync) into a sub-component that only renders when the gate isn't blocking. Effect cleanups unsubscribe from TxEvent / SafeMsgEvent. The hook treats isRequireLoginEnabled === undefined as 'blocking' — that ~1s loading window is where the flash happens. isAlwaysPublic is promoted from a file-local helper to an export so PageLayout can reuse the same allow-list as the route guard. * fix(web): preserve next/query on gate redirects and harden self-redirect checks - /welcome: forward router.query on the redirect to /welcome/spaces so deep links carrying next/safe/chain survive the hop. - /spaces: narrow router.query.spaceId to a non-empty string so duplicate ?spaceId= keys (array) or empty values redirect instead of rendering with a comma-joined value. - CreateSpaceOnboarding: back arrow forwards router.query so users who entered onboarding via a deep link can navigate back without dropping next. - nextUrl: strip trailing slash before the self-redirect blocklist check, and re-validate the resolved URL.pathname so path-traversal (`/foo/..`) collapsing to `/` can no longer slip through. * feat(web): warning border around the viewport while in classic view Persistent visual cue that pairs with the existing ClassicViewToast so users remember they're outside the gated flow. * fix(web): don't bounce existing users into create-space on re-login After logout the spaces RTK Query tag is invalidated. On the render where sign-in completes, the query has `skip` flipping from true to false but hasn't dispatched its fetch yet (that happens in a useEffect after render), so RTK Query returns isFetching=false and currentData=undefined. The hook then read spacesAmount=0 + isSpacesLoading=false and treated the user as new, pushing them into /welcome/create-space. Pass `isFetching || isUninitialized` as `isSpacesLoading` so the skip→unskip transition is also treated as loading. * fix(web): stop /welcome/spaces flash on hydration for opted-in classic users useIsClassicViewOptedIn is backed by useSyncExternalStore, whose server snapshot is hard-coded false. On the first client render after hydration, useIsRequireLoginEnabled therefore read isClassicViewOptedIn=false even when sessionStorage actually said true — long enough for the route guard to fire one redirect to /welcome/spaces before the opt-in propagated. Gate the hook behind an isMounted flag so it returns undefined on the first render, which the route guard already treats as "still loading" and skips the redirect for. After the first effect tick the hook returns the real opt-in-aware value. --------- Co-authored-by: Hanzo Dev <[email protected]>
56 lines
1.5 KiB
Bash
Executable File
56 lines
1.5 KiB
Bash
Executable File
#!/bin/bash
|
|
set -eu
|
|
|
|
# Validate commit message follows conventional commit format
|
|
# Format: type(scope): description OR type: description
|
|
# Examples:
|
|
# feat(auth): add login functionality
|
|
# fix: resolve null pointer exception
|
|
# tests(e2e): fix regression
|
|
# chore(deps): update dependencies
|
|
|
|
commit_msg_file="$1"
|
|
commit_msg=$(cat "$commit_msg_file")
|
|
|
|
# Skip merge commits
|
|
if echo "$commit_msg" | grep -qE "^Merge "; then
|
|
exit 0
|
|
fi
|
|
|
|
# Skip revert commits
|
|
if echo "$commit_msg" | grep -qE "^Revert "; then
|
|
exit 0
|
|
fi
|
|
|
|
# Valid commit types
|
|
types="feat|fix|docs|style|refactor|perf|test|tests|build|ci|chore|revert"
|
|
|
|
# Regex pattern for conventional commits
|
|
# type(scope): description OR type: description
|
|
pattern="^($types)(\([a-zA-Z0-9_-]+\))?: .+"
|
|
|
|
if ! echo "$commit_msg" | head -1 | grep -qE "$pattern"; then
|
|
echo "======================================"
|
|
echo "ERROR: Invalid commit message format!"
|
|
echo "======================================"
|
|
echo ""
|
|
echo "Your commit message:"
|
|
echo " $(head -1 "$commit_msg_file")"
|
|
echo ""
|
|
echo "Expected format:"
|
|
echo " type(scope): description"
|
|
echo " type: description"
|
|
echo ""
|
|
echo "Valid types: feat, fix, docs, style, refactor, perf, test, tests, build, ci, chore, revert"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " feat(auth): add login functionality"
|
|
echo " fix: resolve null pointer exception"
|
|
echo " tests(e2e): fix regression"
|
|
echo " chore(deps): update dependencies"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|