Skip to content

v3.8.0 — Cookie prefix fix (#120) + mobile UX

Choose a tag to compare

@thunpisit thunpisit released this 31 Jul 12:10
· 45 commits to main since this release
5291f70

⚠️ Deploying this logs every admin out, once

The session cookie name changes, so all existing sessions become invalid. Announce before you deploy. Nothing else is affected — no data migration, no re-configuration.


🍪 Cookie prefix — closes the practical half of #120

The session cookie shipped as __Secure-__Host-khaopad_session. Better Auth composes the name as an unconditional concatenation (cookies/index.mjs):

name: `${secureCookiePrefix}${name}`   // "__Secure-" in production

It never checks whether the configured name already carries a prefix, so "__Host-khaopad_session" got doubled. Per RFC 6265bis §4.1.3.2 a prefix only carries its guarantees as the leading prefix — so __Host- was inert. The config bought no subdomain protection while looking like it did.

Verified against the deployed demo, with controls:

Cookie Domain set? Accepted? Means
__Secure-__Host-probe true __Host- not enforced
__Host-probe_control false Correct name → refused. Probe valid.
__Host-probe_valid true Sanity check

curl alone looks healthy here — it doesn't enforce prefix rules. The browser check is what settles it.

Fix: name is now plain khaopad_session; Better Auth emits __Secure-khaopad_session, which browsers do enforce. The __Host--equivalent attributes (path=/, no Domain, httpOnly, SameSite=Lax) are pinned explicitly so a future default change can't relax them.

Real __Host- enforcement needs an upstream Better Auth change; #120 stays open for that. If you have no sibling subdomains on your registrable domain, this changes nothing for you in practice.


📱 Mobile UX

Audited the deployed admin at 375×812 by measuring rendered geometry, not reading markup.

Tables were clipped, not scrollable

Every admin list view truncated on a phone. Wrappers used overflow-hidden, which clips — a 457px table in a 375px viewport lost its right-hand columns with no way to reach them. "Updated" was cut mid-word; the Actions column was unreachable.

13 tables fixed — articles, pages, categories, tags, forms, subscribers, api-keys, content, content/[collection], shop products/orders/discounts, and the product variants table (which had no wrapper at all). overflow-x-auto keeps the rounded-corner clipping the original was there for.

iOS zoomed on every input

Inputs at 14px sit below the 16px threshold where iOS Safari zooms on focus and stays zoomed. Now text-base on mobile, sm:text-sm on desktop.

Tap targets below the 44px minimum

Inputs and buttons were 36px against Apple/Google guidance; icon buttons at 36×36 were worst. Now h-11 on touch, sm:h-9 to preserve desktop density.

Both live in the shared ui/input and ui/button components, so every form in the app benefits.

Consistent permission errors

Eleven routes silently redirected to /admin on insufficient role, while users/audit/settings threw an explanatory 403. Same situation, two behaviours — and a silent bounce leaves the user with no idea why they moved. All now 403 with a reason.


✅ What the audit found clean

Worth recording, so nobody re-investigates: no horizontal page overflow, no console errors, all inputs labelled, alt text present, no unsafe target=_blank, sidebar collapses correctly to a hamburger, and the public site has canonical + hreflang + JSON-LD with a single H1.

The mobile shell was already solid. Every defect was in the shared primitives or the table wrappers — which is why one fix each covered the whole app.


📋 Known gap, not fixed here

No site-wide og:image fallback. Seo.svelte emits og:image only when a page supplies one, so any page without a hero image shares to social with a blank card. Fixing it needs a default asset, which is a design decision rather than a code one.


🚀 Upgrading a fork

git fetch upstream && git merge upstream/main

No migration. No config change. Announce the forced logout, then deploy.

Setup instructions (what belongs at /admin/settings/secrets vs the Cloudflare dashboard) are unchanged from v3.7.1 — that table is still accurate.

Tests

109 (was 103). The cookie-name guard is mutation-verified: restoring __Host- fails 2 tests.

Full changelog: v3.7.1...v3.8.0