marky

Lightweight bookmarking + plaintext archive
Log | Files | Refs | README | LICENSE

commit 664096f3d8ffa4c124248d35d0d619e6281f7d3d
parent 934946a516bc71b0b18e4297f234ae41bceec1e9
Author: David Eisinger <[email protected]>
Date:   Thu,  9 Oct 2025 14:13:52 -0400

Simplify token code

Diffstat:
Minternal/api.go | 94++++++++++++++++++++++++++++++++++++++-----------------------------------------
Minternal/web/app.js | 42+++++++-----------------------------------
2 files changed, 52 insertions(+), 84 deletions(-)

diff --git a/internal/api.go b/internal/api.go @@ -252,43 +252,26 @@ func NewHandlerWithConfig(db *DB, cfg HandlerConfig) (http.Handler, error) { }(), origin, jsToken, jsUser) }) - // API token endpoints for logged-in user - mux.HandleFunc("/api/token", func(w http.ResponseWriter, r *http.Request) { - uid, ok := CurrentUserID(r) - if !ok { - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - switch r.Method { - case http.MethodGet: - reveal := strings.EqualFold(r.URL.Query().Get("reveal"), "1") || strings.EqualFold(r.URL.Query().Get("reveal"), "true") - tok, present, err := db.GetAPIToken(r.Context(), uid) - if err != nil { - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - if !present { - _ = json.NewEncoder(w).Encode(map[string]any{"present": false}) - return - } - if reveal { - _ = json.NewEncoder(w).Encode(map[string]any{"present": true, "api_token": tok}) - } else { - _ = json.NewEncoder(w).Encode(map[string]any{"present": true}) - } - case http.MethodPost: - tok, err := db.RegenerateAPIToken(r.Context(), uid) - if err != nil { - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{"api_token": tok}) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - }) + // API token endpoint for logged-in user (regenerate only) + mux.HandleFunc("/api/token", func(w http.ResponseWriter, r *http.Request) { + uid, ok := CurrentUserID(r) + if !ok { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + switch r.Method { + case http.MethodPost: + tok, err := db.RegenerateAPIToken(r.Context(), uid) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{"api_token": tok}) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } + }) // Public listing by username (JSON) mux.HandleFunc("/u/", func(w http.ResponseWriter, r *http.Request) { @@ -359,18 +342,31 @@ func NewHandlerWithConfig(db *DB, cfg HandlerConfig) (http.Handler, error) { return } } - // Settings page (requires auth) - if p == "settings" { - if uid, ok := userIDFromRequest(r, db); !ok || uid == 0 { - http.Redirect(w, r, "/", http.StatusFound) - return - } - if html, err := RenderPage("settings.html", cfg); err == nil { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, _ = w.Write(html) - return - } - } + // Settings page (requires auth) + if p == "settings" { + uid, ok := userIDFromRequest(r, db) + if !ok || uid == 0 { + http.Redirect(w, r, "/", http.StatusFound) + return + } + if html, err := RenderPage("settings.html", cfg); err == nil { + // Embed API token directly for settings hydration + if tok, _, err := db.GetAPIToken(r.Context(), uid); err == nil { + inject := []byte("<script>window.MARKY_TOKEN=" + strconv.Quote(tok) + "</script>\n") + marker := []byte("<script src=\"/app.js\"></script>") + if i := bytes.Index(html, marker); i >= 0 { + out := make([]byte, 0, len(html)+len(inject)) + out = append(out, html[:i]...) + out = append(out, inject...) + out = append(out, html[i:]...) + html = out + } + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write(html) + return + } + } if p != "" { // If the path is a single segment ending with a trailing dot, treat it // as an invalid username route and return 404. diff --git a/internal/web/app.js b/internal/web/app.js @@ -15,7 +15,7 @@ createApp({ me: null, viewUser: '', auth: { username: '', password: '' }, - api: { present: false, value: '', show: false, busy: false, copied: false }, + api: { value: '', busy: false, copied: false }, }; }, mounted() { @@ -48,9 +48,8 @@ createApp({ // Settings page if (first === 'settings') { if (!this.me || !this.me.username) { location.replace('/'); return; } - // Ensure the API token value is present to render immediately - await this.fetchApiPresence(); - await this.revealApiKey(); + // Token embedded by server + this.api.value = (window.MARKY_TOKEN || ''); this.updateTitle(); return; } @@ -60,7 +59,7 @@ createApp({ this.viewUser = u; if (this.me && this.me.username === this.viewUser) { this.view = 'self'; - this.fetchApiPresence(); + // no token fetch needed here // Initialize from server-embedded data when available try { if (window.MARKY_INITIAL && window.MARKY_INITIAL.view === 'self' && window.MARKY_INITIAL.user === this.me.username) { @@ -97,44 +96,17 @@ createApp({ return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }); } catch { return String(text || ''); } }, - async fetchApiPresence() { - try { - const res = await fetch('/api/token'); - if (!res.ok) return; - const data = await res.json(); - this.api.present = !!data.present; - if (!this.api.present) { this.api.value=''; this.api.show=false; } - } catch {} - }, - async revealApiKey() { - this.api.busy = true; - try { - const res = await fetch('/api/token?reveal=1'); - if (!res.ok) throw new Error('Failed to reveal'); - const data = await res.json(); - this.api.present = true; - this.api.value = data.api_token || ''; - this.api.show = true; - this.api.copied = false; - } catch {} - this.api.busy = false; - }, - async generateApiKey() { + async regenerateApiKey() { this.api.busy = true; try { const res = await fetch('/api/token', { method: 'POST' }); - if (!res.ok) throw new Error('Failed to generate'); + if (!res.ok) throw new Error('Failed to regenerate'); const data = await res.json(); - this.api.present = true; this.api.value = data.api_token || ''; - this.api.show = true; this.api.copied = false; } catch {} this.api.busy = false; }, - async regenerateApiKey() { - await this.generateApiKey(); - }, async copyApiKey() { try { await navigator.clipboard.writeText(this.api.value || ''); @@ -281,7 +253,7 @@ createApp({ bookmarkletHref() { // Build the bookmarklet installer link dynamically const origin = location.origin; - const auth = (this.api.present && this.api.value && this.me && this.me.username) + const auth = (this.api.value && this.me && this.me.username) ? `&user=${encodeURIComponent(this.me.username)}&tok=${encodeURIComponent(this.api.value)}` : ''; const tmpl = "javascript:(function(){try{var d=document,s=encodeURIComponent,u=location.href,t=document.title,sel='';var ae=d.activeElement;if(ae&&((ae.tagName==='TEXTAREA')||(ae.tagName==='INPUT'&&/^(?:text|search|url|tel|email)$/i.test(ae.type)))){var ss=ae.selectionStart||0,se=ae.selectionEnd||0;if(typeof ss==='number'&&typeof se==='number'&&se>ss){sel=ae.value.substring(ss,se)}}if(!sel&&d.getSelection){sel=d.getSelection().toString()}else if(!sel&&window.getSelection){sel=window.getSelection().toString()}else if(!sel&&d.selection){sel=d.selection.createRange().text}var win=window.open('{{ORIGIN}}/bookmarklet?u='+s(u)+'&t='+s(t)+'&d='+s(sel)+'{{AUTH}}','marky','width=520,height=520');if(win){win.focus()}}catch(e){alert('Could not open Marky: '+e)}})();";