Bearer tokens and Basic Auth usernames were used as session IDs without
any format validation, allowing arbitrary strings (including crafted or
very long values) to reach the storage layer as untrusted session IDs.
Restrict accepted session IDs to the exact format produced by
NewSessionID(): standard base32 alphabet [A-Z2-7], exactly 103 chars.
Any token that does not match is ignored, resulting in a new anonymous
session instead of a storage lookup with attacker-controlled input.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, if session deletion failed (e.g. storage error), the error
was silently swallowed. The stale session could still be replayed via
Bearer token even after the client-side cookie was cleared.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The server-side session store (gorilla/sessions backed by DB) reused the
same session ID across login: session.Clear() only zeroed the values map
but left session.ID unchanged. An attacker who planted a known session ID
before authentication retained access after the victim logged in.
Fix with a two-phase save:
1. Delete the old session from the DB (MaxAge=-1 save), expiring the cookie.
2. Reset the underlying gorilla Session.ID to "" so the store generates a
fresh ID, then save the authenticated session with original cookie options
(Secure, Path, MaxAge) preserved via a duck-typed interface assertion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, RecordFailure/RecordSuccess were only called when a captcha
provider was configured, making brute-force tracking entirely inactive
on deployments without one.
- Always track login failures and successes regardless of captcha config
- When threshold is crossed with a captcha provider: 401 + captcha_required (existing behaviour)
- When threshold is crossed without a captcha provider: 429 + rate_limited flag
- Frontend: show a rate-limited message and disable the submit button on 429
- Add errors.rate-limited translation key to all locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the requested email does not exist, the function returned in
microseconds, while a valid email with wrong password took ~100ms
(bcrypt). An attacker could enumerate valid accounts by measuring
response latency.
Add a dummy bcrypt.CompareHashAndPassword call on the not-found path so
both branches take a comparable amount of time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email addresses embedded in error strings could leak to arbitrary log
sinks or error responses as errors propagate. Strip them from the usecase
errors and instead log `IP email: reason` once at the controller level,
keeping fail2ban/CrowdSec-compatible log lines.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a bcryptCost constant to centralize the target cost (12), a
NeedsRehash() method that checks the stored hash cost via bcrypt.Cost(),
and trigger a transparent rehash in AuthenticateUserWithPassword when
the stored hash is below the current target.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OWASP now recommends bcrypt cost >= 12. Using the implicit default cost
of 0 (which maps to 10) is below current recommendations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bcrypt silently truncates input at 72 bytes. Without an explicit maximum,
a user could set a 200-char password and log in with only the first 72
chars, and very long passwords could be used for a CPU-based DoS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SHA-1 is cryptographically broken. Replace with SHA-256 and slice to
the first 6 bytes (12 hex chars) for a compact, human-readable token
fingerprint. 48 bits is more than sufficient to distinguish a handful
of active sessions without sacrificing readability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DeleteSession was returning 200 with a null body instead of the
semantically correct 204 No Content. Updated the Swagger annotation
to match the new status code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The field existed but was never written, making it useless for security
auditing. Record the time at each successful login and persist it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces ProviderPicker and PickProvider reusable components so that
adding a domain from NewDomainInput or FilterDomainInput opens an
inline provider-selection modal instead of navigating away to
/domains/new/:dn.
Replace the ListGroup-based provider list with a Bootstrap Table on the
providers page. Rows are clickable to edit, the domain count links to
the domains page pre-filtered by provider, and action buttons handle
propagation correctly.
Replace the dedicated provider type selection page with a modal,
using a module-level controller pattern. The /providers/new route
now redirects to /providers?newProvider, which auto-opens the modal.
Introduces a reusable PageTitle component with a teal overline accent,
display-3 heading, optional monospace domain label, subtitle, and a
children slot for future domain health/check badges. Applied consistently
to the zone viewer, history, logs, export, import, resolver, providers,
account settings, and new-domain pages.
Introduce ServiceDetailsOffcanvas, an offcanvas panel that opens when
clicking a service card. It displays the service description, its DNS
records, and provides actions buttons.
Also remove raw DNS record from service form.
Install highlight.js and apply DNS zone file syntax highlighting on the
export page and in the RecordText component. Uses the github theme and
imports only the dns language to keep the bundle small.
Convert the zone file viewer from a modal dialog to a dedicated page at
/domains/[dn]/export, following the same pattern used for service pages.
Adds a "Copy to clipboard" button in the page title bar and adds the
common.copy-clipboard translation key to all supported locales.
Introduce a helpLinkOverride store so the Header's help button can
display context-sensitive service docs. Move the svctype-to-URL
computation into Help.svelte (service prop + $effect), removing the
duplicated helpLink functions from the service edit page and the modal
Footer. Pages now render <HelpButton {service} /> to drive the override
without showing a redundant per-page button.
Replace the Service modal component with a dedicated service page route
and a ServiceSidebar component, improving navigation by giving each
service its own URL under [subdomain]/[serviceid].
Use IntersectionObserver to track which subdomain section is currently
visible in the top 30% of the viewport, bold the matching sidebar link,
and auto-scroll the sidebar to keep it in view with scroll-margin-block
so adjacent items remain visible.
Split subdomain display to show the subdomain and domain parts
separately, making the root domain bold and dimming the domain
suffix. Add hover bold effect for text-dark links.
Refactor the domain layout by moving the zone-specific sidebar content
(subdomain list, zone actions dropdown) into a new ZoneSidebar.svelte
component, improving separation of concerns between zone and service views.
- Add network fallback for asset cache misses (prevents broken requests
on install race conditions)
- Fix query string stripping to use a clean Request instead of copying
event.request options
- Await cache.put() calls to prevent incomplete writes on SW termination
- Expand auth path exclusion to startsWith("/api/auth") to cover all
auth-related endpoints