commit 664096f3d8ffa4c124248d35d0d619e6281f7d3d
parent 934946a516bc71b0b18e4297f234ae41bceec1e9
Author: David Eisinger <[email protected]>
Date: Thu, 9 Oct 2025 14:13:52 -0400
Simplify token code
Diffstat:
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)}})();";