feat: osv-scanner in the deps job + desktop welcome sign-in modal

- update-deps.sh now runs osv-scanner (OSV DB) pre/post update, alongside npm
  audit; it immediately found 107 known vulns (5 critical) npm audit missed
  (vite/vitest dev deps, xlsx with no fix, zod patch).
- Layout opens the themed LoginDialog once for a logged-out desktop visitor
  (non-native, >=1024px, ~900ms), dismissible and remembered; it also offers
  SignupDialog. Reuses the existing extension/bunker/Amber/nsec/NostrConnect
  methods.
This commit is contained in:
SkateSpots 2026-10-26 10:59:53 +07:00
parent 4d9389fa48
commit aa2a70e9fd
3 changed files with 77 additions and 1 deletions

View file

@ -1522,6 +1522,16 @@ session with **no other memory of this project**. So:
(which also `saveLastLocation()`s), so `MapPage`'s mount-time
`getLastLocation()` opens centered on their area. The logged-out wallet
section's dead disabled "Log in first" button became a plain line.
- **Desktop welcome sign-in modal (2026-10-26):** `Layout` opens the themed
`LoginDialog` **once** for a logged-out **desktop** visitor (non-native,
`min-width: 1024px`, after ~900 ms) as a welcome/sign-in offer; dismissal is
remembered in `skatespots:desktop-welcome-dismissed`, and it also offers the
normal `SignupDialog`. It reuses the existing methods (browser extension,
bunker/signer, Amber on Android, nsec, and the **NostrConnect QR**), which is
what the reference screenshot showed — the dialog is still **tabbed**, not
the reference's 3-card layout (a restyle would need a visual QA pass).
⚠️ `SignupDialog` is a **default-only** export, so lazy-load it as
`lazy(() => import('…/SignupDialog'))`.
- **First-run checklist + backup-reminder gating (fixed 2026-10-12).** Two
onboarding prompts nagged returning users:
1. `FirstRunChecklist` (map) showed for **any logged-in user** until they
@ -2268,6 +2278,16 @@ Rules:
bounds the push (`timeout 180`) since the git remote signs via the remote signer.
Timer: `scripts/install-deps-timer.sh` → `skatespots-deps.timer` (daily tick).
Flags: `--scan-only`, `--no-push`, `--force`. Logs: `journalctl --user -u skatespots-deps`.
**osv-scanner added (2026-10-26):** the scan block now also runs
`osv-scanner scan source -L package-lock.json` (binary at
`~/go/bin/osv-scanner`, installed with `go install
github.com/google/osv-scanner/v2/cmd/osv-scanner@latest`; override with
`OSV_BIN`), pre- and post-update, report-only. ⚠️ It immediately found **107
known vulnerabilities (5 critical, 53 high)** that `npm audit` was NOT
surfacing — mostly **dev-only** `vite`/`vitest` (patchable within-range) plus
`xlsx@0.18.5` (two high; **no fix published**) and `zod` (patch). Dev-dep CVEs
don't ship to users; `xlsx` is lazy-loaded for exports. Run `--force` to apply
the fixable ones (gated on tests).
### Community relay — self-hosted Newlay (2026-10-12)

View file

@ -28,6 +28,9 @@ STATE_DIR="${DEPS_STATE_DIR:-$HOME/.local/state/skatespots-deps}"
mkdir -p "$STATE_DIR"
LAST="$STATE_DIR/last-run"
REPORT="$STATE_DIR/report-$(LC_ALL=C date +%F).txt"
# OSV-Scanner (broader than npm audit: queries the OSV database for the lockfile).
# Installed with: go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest
OSV_BIN="${OSV_BIN:-$HOME/go/bin/osv-scanner}"
SCAN_ONLY=0; NO_PUSH=0; FORCE=0
for a in "$@"; do
@ -64,6 +67,13 @@ fi
echo
echo "=== npm audit ==="
npm audit 2>&1 || true
echo
echo "=== osv-scanner (OSV database) ==="
if [ -x "$OSV_BIN" ]; then
"$OSV_BIN" scan source -L package-lock.json 2>&1 || true
else
echo "osv-scanner not installed (expected $OSV_BIN)"
fi
} | tee "$REPORT"
if [ "$SCAN_ONLY" -eq 1 ]; then
@ -86,6 +96,8 @@ npm update 2>&1 | tail -3
npm audit fix 2>&1 | tail -3
{
echo; echo "=== post-update npm audit ==="; npm audit 2>&1 || true
echo; echo "=== post-update osv-scanner ==="
if [ -x "$OSV_BIN" ]; then "$OSV_BIN" scan source -L package-lock.json 2>&1 || true; fi
} | tee -a "$REPORT"
# ── gate: full tests + boot smoke ───────────────────────────────────────────

View file

@ -1,4 +1,4 @@
import { useState, useRef, lazy, Suspense } from 'react';
import { useState, useRef, useEffect, lazy, Suspense } from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { cn } from '@/lib/utils';
@ -81,6 +81,7 @@ const LazySetStatusDialog = lazy(() =>
const LazyLoginDialog = lazy(() =>
import('@/components/auth/LoginDialog').then((m) => ({ default: m.LoginDialog })),
);
const LazySignupDialog = lazy(() => import('@/components/auth/SignupDialog'));
const LazyBackupReminder = lazy(() =>
import('@/components/BackupReminder').then((m) => ({ default: m.BackupReminder })),
);
@ -516,6 +517,26 @@ export function Layout({ children, noPadding }: LayoutProps) {
const [showAddAccount, setShowAddAccount] = useState(false);
const { currentMessage, dismissCurrentMessage } = useSasquatchGuide();
const { user } = useCurrentUser();
// Desktop, logged-out, first visit: offer the sign-in modal once. Reuses the
// themed LoginDialog/SignupDialog (extension, bunker/signer, Amber, nsec,
// NostrConnect QR). Dismissal is remembered so it never nags again.
const WELCOME_LOGIN_DISMISSED = 'skatespots:desktop-welcome-dismissed';
const [showWelcomeLogin, setShowWelcomeLogin] = useState(false);
const [showWelcomeSignup, setShowWelcomeSignup] = useState(false);
const dismissWelcomeLogin = () => {
try { localStorage.setItem(WELCOME_LOGIN_DISMISSED, '1'); } catch { /* ignore */ }
setShowWelcomeLogin(false);
};
useEffect(() => {
if (user || isNativePlatform()) return;
try {
if (localStorage.getItem(WELCOME_LOGIN_DISMISSED) === '1') return;
} catch { return; }
if (typeof window === 'undefined' || !window.matchMedia?.('(min-width: 1024px)').matches) return;
const timer = window.setTimeout(() => setShowWelcomeLogin(true), 900);
return () => window.clearTimeout(timer);
}, [user]);
const { t } = useTranslation();
const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts();
const author = useAuthor(user?.pubkey || '');
@ -1163,6 +1184,29 @@ export function Layout({ children, noPadding }: LayoutProps) {
/>
</Suspense>
{/* Desktop, logged-out, first visit — one-time sign-in offer */}
<Suspense fallback={null}>
<LazyLoginDialog
isOpen={showWelcomeLogin}
onClose={dismissWelcomeLogin}
onLogin={dismissWelcomeLogin}
onSignup={() => {
dismissWelcomeLogin();
setShowWelcomeSignup(true);
}}
/>
</Suspense>
<Suspense fallback={null}>
<LazySignupDialog
isOpen={showWelcomeSignup}
onClose={() => setShowWelcomeSignup(false)}
onUseAmber={() => {
setShowWelcomeSignup(false);
setShowWelcomeLogin(true);
}}
/>
</Suspense>
{/* Newsletter: floating pill + Monday admin reminder */}
<NewsletterOverlay />