feat(account): nostr login#911
Conversation
Add password field to Session type so the backup modal can show credentials later. The password is generated in doSignUp() and was previously discarded after the API call. Old sessions are backfilled with an empty string — the password is lost but the token still works. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the bookmark link in the header with a user icon that opens a dropdown menu with "My Saved" and "Log out". Uses svelte-outclick for outside click handling (same as NavDropdownDesktop). Only visible when the user has a session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add test for password backfill when loading old sessions without the password field (mirrors existing savedAreas backfill test) - Add aria-haspopup="true" to UserMenu trigger button for screen readers (matches NavDropdownDesktop pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UserMenu is now always visible: - Logged out: outline account icon, dropdown shows "Log in" - Logged in: filled account icon, dropdown shows "My Saved" + "Log out" Links to /login which will be built in the next step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every user gets an auto-generated account on first save. "Log out" made no sense — clicking Save again would just create another throwaway. Account switching will be handled by the login flow which replaces the current session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /api/session/login server route proxying token creation - /login page with username + password form - session.login() method to replace current session - After login, fetches saved places/areas from server to populate store - Redirects to /saved on success - Error toast on invalid credentials or server error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Since every user always has an account (auto-generated on first save), they need a way to switch to a different account without a logout step. Links to /login which replaces the current session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hovering the user icon now shows the account username (e.g. "glossy-wealth-1878") via the title attribute. Shows "Account" when not logged in. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user clicks Save for the first time and the throwaway account is silently created, show a success toast: "Account created — your saved places are stored on this device." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Theme toggle is h-10 w-10. User menu trigger was unsized, causing visual misalignment. Match the dimensions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
session.init() is a synchronous localStorage read but was deferred to onMount, causing a visible 2-second delay before the user icon rendered. Move it to the module level with a browser guard so it runs during script evaluation, before the first render. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This reverts commit e62dfd8.
- "Switch account" now shows a confirmation dialog warning that the current saved places will be replaced - Remove unused nav.logout i18n key - Update security comment in session.ts to acknowledge password storage in localStorage alongside the token Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BackupModal shows username + password with copy buttons and show/hide toggle. Accessible from "Back up account" in UserMenu. - Old accounts without a stored password show "Not available" - Remove switch account confirmation — if the user knows their credentials (just logged in), switching is safe - Remove dead switchAccountConfirm i18n key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add autoGenerated flag to Session type. Set true in signUp(), false in login(). UserMenu only shows "Back up account" when the account was auto-generated — users who logged in with known credentials don't need to back them up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When logging in with known credentials, the user already has their password. No reason to keep it in localStorage where an XSS could exfiltrate it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redirecting anonymous users to /map was confusing — they had no idea why they landed there. Now /saved just shows the empty state message regardless of session status. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the user visits /saved without an account, redirect to /login where they can log in with existing credentials or learn they need to save something first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reject oversized username (>100) or password (>200) before forwarding to the upstream API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users who want a custom username can create an account on developer.btcmap.org, then log in on the main site. Added "Don't have an account? Create one here" below the login form. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Close BackupModal on Escape key press
- Remove document.execCommand("copy") fallback — navigator.clipboard
is the standard API and works on all modern browsers over HTTPS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users start with no account, so "Switch account" was confusing — it implies multiple accounts. "Log out" is clearer: it means "forget this session." If they want a different account, they log out first, then log in. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Header renders UserMenu twice (desktop + mobile), producing duplicate DOM IDs. Use a random suffix per instance so each trigger has a unique ID and the OutClick exclusion works correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap clipboard.writeText in try/catch to prevent unhandled rejections (console.error only, no error toast — failure is extremely unlikely on HTTPS) - Replace hardcoded "copied" string with i18n key backup.copied Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The security comment said both token and password are stored, but manual logins intentionally store an empty password. Clarify that password persistence only applies to auto-generated sessions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Stop trimming password input — spaces may be valid characters in credentials. Username is still trimmed (no valid username has leading/trailing spaces). - Sanitize console.error in login server route and client page to only log the HTTP status code, not the full error object which may contain request headers with credentials. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents unintended form submits if the component is ever rendered inside a form element. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tton Use role=presentation on the backdrop overlay instead of suppressing a11y warnings. The backdrop is decorative — keyboard interaction is handled via svelte:window Escape handler. Add type=button to the close button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Math.random() trigger ID with a prop-based ID to avoid SSR/client hydration mismatches. Header passes distinct IDs for desktop and mobile instances. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Group authenticated user pages under /user/ to establish a clear URL pattern for future user features (profile, feed, settings). Login stays at /login since it's a public entry point. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from filled bookmark (ic:baseline-bookmark) to bookmark with checkmark (ic:baseline-bookmark-added) for the saved state. Aligns with the Android app's icon pattern — the checkmark is a more explicit signal than fill alone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Needed for the upcoming "Sign in with Nostr" flow on the login page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server route that proxies signed NIP-98 events to POST /v4/auth/nostr and returns a Bearer token + username. Same CORS-avoidance pattern as the existing /api/session/login and signup routes. The v4/auth/nostr endpoint is not yet live; this route returns 502 until the API ships it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows a "Sign in with Nostr extension" button on /login when window.nostr is detected (Alby, nos2x, etc.). Signs a NIP-98 kind 27235 event and posts it to the new /api/session/nostr proxy, exchanging it for a Bearer token. nsec paste fallback for mobile comes in a follow-up commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user doesn't have a NIP-07 extension (common on mobile), they can paste their nsec key. The key is used once locally to sign a NIP-98 event, then zeroed and never persisted or sent over the network. The toggle is shown alongside the extension button — on mobile it becomes the primary path since window.nostr is usually absent. Follows the Primal mobile-web pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Replaces duplicate Promise.allSettled hydration blocks in the two /login code paths (username+password and Nostr) with a single session method. Next login path (backup code, OAuth, etc.) gets it for free. Addresses Copilot review comment on PR #911. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, any error without err.response was treated as a decode error. Network, CORS, or timeout failures have no .response either, so transport problems were surfaced as "invalid nsec" — misleading to the user. Split into two phases: - Phase 1 (sync decode + sign): any throw → "invalid nsec" - Phase 2 (async exchange): uses the same 401-vs-generic classifier as the extension login path Addresses Copilot review comment on PR #911. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Return a cleanup function from onMount so the 300ms re-check doesn't fire after the component is destroyed. Prevents a set-after-unmount if the user navigates away quickly. Addresses Copilot review comment on PR #911. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…903 The client signs a NIP-98 event with NOSTR_AUTH_URL in the "u" tag; the proxy route POSTs to that same URL. Hardcoding it in both places risks silent signature-verification failures if one drifts (e.g. staging URL). Single source of truth in $lib/nostr. Addresses Copilot review comment on PR #911. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reject bodies with Content-Length > 4096 bytes before parsing. A valid NIP-98 event is ~400 B; 4 KB leaves headroom without accepting obvious junk. Prevents accidental misuse of the proxy as a parser for large payloads. Deliberate DoS still needs upstream rate limiting. Addresses the one Medium finding from the branch security review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"off" is widely ignored by password managers on type=password inputs. "new-password" is the documented hint Chrome and Safari respect to suppress save/fill prompts. Reduces the chance a user's nsec is silently stored by a password manager. Addresses a Low finding from the branch security review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The signed NIP-98 event's `u` tag must exactly match the URL the API reconstructs and verifies. Hardcoding api.btcmap.org makes local dev impossible — running btcmap-api on http://127.0.0.1:8000 and signing for api.btcmap.org would always fail the URL-match check. Read VITE_API_BASE_URL with the production endpoint as the default, strip any trailing slash, then derive NOSTR_AUTH_URL from it. The SvelteKit proxy that forwards to the API uses the same constant, so both the signed event and the API hop stay aligned with whatever the env var points at. For local dev: set VITE_API_BASE_URL=http://127.0.0.1:8000 in .env. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /v4/auth/nostr API extractor reads the signed event from the
canonical Authorization: Nostr <base64(event)> header, not from the
request body. This was a transport mismatch — the proxy was sending
{signed_event} as JSON body, which the API wouldn't see.
Switch the API hop to base64-encode the signed event JSON and put it
in the Authorization header, with no body. The browser-side contract
of this route is unchanged: the client still POSTs {signed_event} to
/api/session/nostr, the SvelteKit server route does the reshape.
The 4 KB content-length cap on the incoming body still applies, since
the {signed_event} envelope from the browser is what's bounded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…base #903 Adds src/lib/api-base.ts (cherry-picked verbatim from main, where it already exists in this exact form for the same reason) so every SvelteKit server-route proxy in src/routes/api/session/* resolves the btcmap-api base URL the same way: VITE_API_BASE_URL > 'https://api.btcmap.org' (default), trailing-/ stripped. Without this, /user/saved was failing locally with 401: a token minted against the local API at http://127.0.0.1:8000 doesn't authenticate against api.btcmap.org, but the saved-places / saved-areas proxies were pointing at production regardless of VITE_API_BASE_URL. Affected proxies, all hardcoded api.btcmap.org before: - POST /api/session/login - POST /api/session/signup - GET/PUT /api/session/saved-places - GET/PUT /api/session/saved-areas Also drops the inline VITE_API_BASE_URL read introduced in 63e48c9 from src/lib/nostr.ts in favour of the shared constant — same end value, just de-duped. Out of scope here: the rest of the codebase still has ~25 hardcoded api.btcmap.org references in map sync, merchant/area page loaders, etc. Those will surface separately if local-dev coverage is broadened, and should rebase cleanly once #911 lands on top of current main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without an explicit navigation, clicking Logout from /user/saved
cleared the session in localStorage but kept the user on the same
page. The page only checks for a session in onMount, so it kept
rendering the post-login UI — and any subsequent saved-* fetch
fired without a token and surfaced as "Failed to load some saved
items".
After session.clear(), goto('/login') so the user lands on a
clean state and can sign back in if they want.
✅ Deploy Preview for btcmap ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Superseded by #990 ? |
|
Yes! |

No description provided.