Skip to content

Add token auth for live-server endpoints#23

Merged
selimacerbas merged 1 commit into
mainfrom
feat/token-auth
May 24, 2026
Merged

Add token auth for live-server endpoints#23
selimacerbas merged 1 commit into
mainfrom
feat/token-auth

Conversation

@selimacerbas
Copy link
Copy Markdown
Owner

Summary

Plumbs token-based authentication through the live-server endpoints so that with PR #13 (host binding to 0.0.0.0) about to land, LAN-exposed previews are actually safe to recommend instead of just warned about. Caller experience does not change: token is auto-generated, embedded in the auto-opened browser URL, and stashed in sessionStorage. No new user config to set.

Depends on

selimacerbas/live-server.nvim#4 (adds cfg.token and cfg.protected_paths). Merge that one first.

What changes

assets/index.html

  • New data-live-token="__LIVE_TOKEN__" attribute on <html>.
  • LIVE_TOKEN is read from the data attribute, then the URL ?t= param, then sessionStorage, in that order, and stashed in sessionStorage for future loads.
  • withToken(url) helper appends ?t=<token> to URLs.
  • Used by the existing fetch('content.md') and new EventSource('/__live/events') calls.

lua/markdown_preview/init.lua

  • Generates a 128-bit hex token via live_server.util.random_token(16).
  • Substitutes __LIVE_TOKEN__ in index.html (using function-form gsub to dodge the %-in-replacement-string Lua gotcha).
  • Passes token = M._token and protected_paths = { "^/content%.md$" } to ls_server.start.
  • Embeds the token in the auto-opened browser URL via a small browser_url(port) helper.
  • Restructures M.start() so the role decision (primary vs secondary) and token resolution happen before write_index. Secondary instances read the token from the lockfile and adopt it.
  • M.stop() clears _token.

lua/markdown_preview/lock.lua

  • Adds a token field to the lock JSON.
  • Lockfile mode tightened to 0600 so the secret isn't world-readable on multi-user systems.

lua/markdown_preview/remote.lua

  • send_event(port, type, json, token) now appends &t=<token> to the URL it builds, so the secondary scroll-sync RPC works against an auth-enabled primary.

What does not change

  • Default host stays 127.0.0.1 (changed by PR Add configurable host binding for external access #13, not here).
  • No new user-facing config option. The token is internal.
  • Static assets (index.html) remain reachable without a token; only content.md and the two live-control endpoints are gated. This matters because the browser bootstraps from index.html before any JS runs.

Tests

tests/token_auth_test.lua, 12 cases:

  • Server instance + token created, 32 hex chars.
  • HTML response contains data-live-token="<current token>".
  • / reachable without token (200).
  • /content.md returns 401 without token, 200 with correct token; body contains the buffer text.
  • M.stop() clears _token and _server_instance; port no longer responds.

Run: nvim --headless --clean -c "set rtp+=/path/to/live-server.nvim" -c "set rtp+=." -c "luafile tests/token_auth_test.lua" -c "qa!"

Relation to PR #13

PR #13 adds the host binding option with a README warning that the LAN exposure has no auth. This PR makes that warning soften-able: once both are merged, host = "0.0.0.0" is genuinely safe because anyone trying to read content.md or hit /__live/inject from another machine needs the 128-bit token from the browser URL.

🤖 Generated with Claude Code

Generates a random 128-bit token at server start and threads it through:

  * assets/index.html         data-live-token attribute, withToken() helper
                              wrapping the content.md fetch and the SSE
                              EventSource URL. Token also read from URL
                              query and stashed in sessionStorage so
                              refreshes work without it on the URL.
  * lua/.../init.lua          ls_util.random_token() at server start,
                              substituted into index.html via __LIVE_TOKEN__,
                              passed to ls_server.start as cfg.token and
                              cfg.protected_paths = { "^/content%.md$" },
                              embedded in the auto-opened browser URL.
  * lua/.../lock.lua          token field in the lock JSON, mode 0600 so
                              the secret isn't world-readable on multi-user
                              systems. Secondary instances read it back.
  * lua/.../remote.lua        send_event(port, type, json, token); the
                              secondary scroll-sync RPC now includes the
                              token it read from the lockfile.

Static assets (index.html itself) remain reachable without a token so the
browser can bootstrap. content.md and both live-control endpoints are gated.

Depends on live-server.nvim cfg.token / cfg.protected_paths support.

Tests: 12/12 in tests/token_auth_test.lua covering token generation,
HTML substitution, content.md gating, and stop() cleanup.
@selimacerbas selimacerbas merged commit dfe1ce6 into main May 24, 2026
@selimacerbas selimacerbas deleted the feat/token-auth branch May 24, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant