apply CSP patches. no more inline script or css.

from timkuijsten
This commit is contained in:
Ted Unangst 2023-01-26 15:56:48 -05:00
parent 78a58539ea
commit 2983156e4b
13 changed files with 245 additions and 91 deletions

View File

@ -3,12 +3,12 @@
<div class="info">
{{ .AboutMsg }}
<p>
<table style="font-size:0.8em">
<table class="font08em">
<tbody>
<tr><td>version:<td style="text-align:right">{{ .HonkVersion }}
<tr><td>memory:<td style="text-align:right">{{ printf "%.02f" .Sensors.Memory }}MB
<tr><td>uptime:<td style="text-align:right">{{ printf "%.02f" .Sensors.Uptime }}s
<tr><td>cputime:<td style="text-align:right">{{ printf "%.02f" .Sensors.CPU }}s
<tr><td>version:<td class="textright">{{ .HonkVersion }}
<tr><td>memory:<td class="textright">{{ printf "%.02f" .Sensors.Memory }}MB
<tr><td>uptime:<td class="textright">{{ printf "%.02f" .Sensors.Uptime }}s
<tr><td>cputime:<td class="textright">{{ printf "%.02f" .Sensors.CPU }}s
</table>
<p>
</div>

View File

@ -1,4 +1,5 @@
{{ template "header.html" . }}
<script src="/misc.js{{ .MiscJSParam }}" defer></script>
<main>
<div class="info">
<p>
@ -10,14 +11,8 @@
<p><label for=noise>noise:</label><br>
<textarea name="noise" id="noise"></textarea>
<p><button name="chonk" value="chonk">chonk</button>
<label class=button id="donker">attach: <input onchange="updatedonker(this);" type="file" name="donk"><span></span></label>
<label class=button id="donker">attach: <input type="file" name="donk"><span></span></label>
</form>
<script>
function updatedonker(el) {
el = el.parentElement
el.children[1].textContent = el.children[0].value.slice(-20)
}
</script>
</div>
{{ $chonkcsrf := .ChonkCSRF }}
{{ range .Chatter }}
@ -59,7 +54,7 @@ chatter: {{ .Target }}
<p><label for=noise>noise:</label><br>
<textarea name="noise" id="noise"></textarea>
<p><button name="chonk" value="chonk">chonk</button>
<label class=button id="donker">attach: <input onchange="updatedonker(this);" type="file" name="donk"><span></span></label>
<label class=button id="donker">attach: <input type="file" name="donk"><span></span></label>
</form>
</section>
{{ end }}

View File

@ -6,7 +6,7 @@
{{ range .Combos }}
<section class="honk">
<header>
<p style="font-size: 1.8em"><a href="/c/{{ . }}">{{ . }}</a>
<p class="font18em"><a href="/c/{{ . }}">{{ . }}</a>
</header>
</section>
{{ end }}

View File

@ -6,14 +6,11 @@
{{ if .LocalStyleParam }}
<link href="/local.css{{ .LocalStyleParam }}" rel="stylesheet">
{{ end }}
<style>
{{ .UserStyle }}
</style>
<link href="/icon.png" rel="icon">
<meta name="theme-color" content="#305">
<meta name="viewport" content="width=device-width">
</head>
<body>
<body{{ if .UserStyle}} {{ .UserStyle }}{{ end }}>
<header>
{{ if .UserInfo }}
<details id="topmenu">
@ -22,7 +19,7 @@
<li><a id="homelink" href="/">home</a>
<li><a id="atmelink" href="/atme">@me<span id=mecount>{{ if .UserInfo.Options.MeCount }}({{ .UserInfo.Options.MeCount }}){{ end }}</span></a>
<li><a id="firstlink" href="/first">first</a>
<li style="list-style-type:none; margin-left:-1em">
<li class="details">
<details>
<summary>combos</summary>
<ul>
@ -39,7 +36,7 @@
<li><a href="/honkers">honkers</a>
<li><a href="/hfcs">filters</a>
<li><a href="/account">account</a>
<li style="list-style-type:none; margin-left:-1em">
<li class="details">
<details>
<summary>more stuff</summary>
<ul>
@ -59,7 +56,7 @@
</details>
<div id="topspacer">
<p>
<p class="nophone" onclick="window.scrollTo(0,0)">top
<p class="nophone">top
</div>
{{ else }}
<div id="topmenu">

View File

@ -1,4 +1,4 @@
<article class="honk {{ .Honk.Style }}" data-convoy="{{ .Honk.Convoy }}">
<article class="honk {{ .Honk.Style }}" data-convoy="{{ .Honk.Convoy }}" data-hname="{{ .Honk.Handles }}" data-xid="{{ .Honk.XID }}" data-id="{{ .Honk.ID }}">
{{ $bonkcsrf := .BonkCSRF }}
{{ $IsPreview := .IsPreview }}
{{ $maplink := .MapLink }}
@ -30,7 +30,7 @@
<span class="clip"><a href="{{ .URL }}" rel=noreferrer>{{ .What }}</a> {{ .Date.Local.Format "02 Jan 2006 15:04 -0700" }}</span>
{{ if .Oonker }}
<br>
<span style="margin-left: 1em;" class="clip">
<span class="left1em clip">
{{ if $bonkcsrf }}
original: <a class="honkerlink" href="/h?xid={{ .Oonker }}" data-xid="{{ .Oonker }}">{{ .Oondle }}</a>
{{ else }}
@ -40,14 +40,14 @@ original: <a href="{{ .Oonker }}" rel=noreferrer>{{ .Oondle }}</a>
{{ else }}
{{ if .RID }}
<br>
<span style="margin-left: 1em;" class="clip">
<span class="left1em clip">
in reply to: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
</span>
{{ end }}
{{ end }}
<br>
{{ if $bonkcsrf }}
<span style="margin-left: 1em;" class="clip">convoy: <a class="convoylink" href="/t?c={{ .Convoy }}">{{ .Convoy }}</a></span>
<span class="left1em clip">convoy: <a class="convoylink" href="/t?c={{ .Convoy }}">{{ .Convoy }}</a></span>
{{ end }}
</header>
<p>
@ -95,7 +95,7 @@ in reply to: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
{{ .Honk.Guesses }}
<p>{{ .Honk.Noise }}
{{ else }}
<button onclick="return playit(this, '{{ .Honk.Noise }}', '{{ .Honk.Wonkles }}', '{{ .Honk.XID }}')">it's play time!</button>
<button class="playit" data-noise="{{ .Honk.Noise }}" data-wonk="{{ .Honk.Wonkles }}">it's play time!</button>
{{ end }}
{{ end }}
{{ if and $bonkcsrf (not $IsPreview) }}
@ -106,41 +106,41 @@ in reply to: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
<p>
{{ if .Honk.Public }}
{{ if .Honk.IsBonked }}
<button onclick="return unbonk(this, '{{ .Honk.XID }}');">unbonk</button>
<button class="unbonk">unbonk</button>
{{ else }}
<button onclick="return bonk(this, '{{ .Honk.XID }}');">bonk</button>
<button class="bonk">bonk</button>
{{ end }}
{{ else }}
<button disabled>nope</button>
{{ end }}
<button onclick="return showhonkform(this, '{{ .Honk.XID }}', '{{ .Honk.Handles }}');"><a href="/newhonk?rid={{ .Honk.XID }}">honk back</a></button>
<button onclick="return muteit(this, '{{ .Honk.Convoy }}');">mute</button>
<button onclick="return showelement('evenmore{{ .Honk.ID }}')">even more</button>
<button class="honkback"><a href="/newhonk?rid={{ .Honk.XID }}">honk back</a></button>
<button class="mute">mute</button>
<button class="evenmore">even more</button>
</div>
<div id="evenmore{{ .Honk.ID }}" style="display:none">
<div id="evenmore{{ .Honk.ID }}" class="hide">
<p>
<button onclick="return zonkit(this, '{{ .Honk.XID }}');">zonk</button>
<button class="zonk">zonk</button>
{{ if .Honk.IsAcked }}
<button onclick="return flogit(this, 'deack', '{{ .Honk.XID }}');">deack</button>
<button class="flogit-deack">deack</button>
{{ else }}
<button onclick="return flogit(this, 'ack', '{{ .Honk.XID }}');">ack</button>
<button class="flogit-ack" >ack</button>
{{ end }}
{{ if .Honk.IsSaved }}
<button onclick="return flogit(this, 'unsave', '{{ .Honk.XID }}');">unsave</button>
<button class="flogit-unsave">unsave</button>
{{ else }}
<button onclick="return flogit(this, 'save', '{{ .Honk.XID }}');">save</button>
<button class="flogit-save">save</button>
{{ end }}
{{ if .Honk.IsUntagged }}
<button disabled>untagged</button>
{{ else }}
<button onclick="return flogit(this, 'untag', '{{ .Honk.XID }}');">untag me</button>
<button class="flogit-untag">untag me</button>
{{ end }}
<button><a href="/edit?xid={{ .Honk.XID }}">edit</a></button>
{{ if not (eq .Badonk "none") }}
{{ if .Honk.IsReacted }}
<button disabled>badonked</button>
{{ else }}
<button onclick="return flogit(this, 'react', '{{ .Honk.XID }}');">{{ .Badonk }}</button>
<button class="flogit-react" >{{ .Badonk }}</button>
{{ end }}
{{ end }}
</div>

View File

@ -1,4 +1,5 @@
{{ template "header.html" . }}
<script src="/misc.js{{ .MiscJSParam }}" defer></script>
<main>
<div class="info">
<p>
@ -21,21 +22,13 @@
</div>
{{ $honkercsrf := .HonkerCSRF }}
<div class="info">
<script>
function expandstuff() {
var els = document.querySelectorAll(".honk details")
for (var i = 0; i < els.length; i++) {
els[i].open = true
}
}
</script>
<p><button onclick="expandstuff()">expand</button>
<p><button class="expand">expand</button>
</div>
{{ range .Honkers }}
<section class="honk">
<header>
<img alt="avatar" src="/a?a={{ .XID }}">
<p style="font-size: 1.8em"><a href="/h/{{ .Name }}">{{ .Name }}</a>
<p class="font18em"><a href="/h/{{ .Name }}">{{ .Name }}</a>
</header>
<p>
<details>

View File

@ -1,6 +1,6 @@
<p id="honkformhost">
<button id="honkingtime" onclick="return showhonkform();" {{ if .IsPreview }}style="display:none"{{ end }}><a href="/newhonk">it's honking time</a></button>
<form id="honkform" action="/honk" method="POST" enctype="multipart/form-data" {{ if not .IsPreview }}style="display: none"{{ end }}>
<button id="honkingtime" {{ if .IsPreview }}class="hide"{{ end }}><a href="/newhonk">it's honking time</a></button>
<form id="honkform" action="/honk" method="POST" enctype="multipart/form-data" {{ if not .IsPreview }}class="hide"{{ end }}>
<input type="hidden" name="CSRF" value="{{ .HonkCSRF }}">
<input type="hidden" name="updatexid" id="updatexidinput" value = "{{ .UpdateXID }}">
<input type="hidden" name="rid" id="ridinput" value="{{ .InReplyTo }}">
@ -9,12 +9,12 @@
<details>
<summary>more options</summary>
<p>
<label class=button id="donker">attach: <input onchange="updatedonker();" type="file" name="donk"><span>{{ .SavedFile }}</span></label>
<label class=button id="donker">attach: <input type="file" name="donk"><span>{{ .SavedFile }}</span></label>
<input type="hidden" id="saveddonkxid" name="donkxid" value="{{ .SavedFile }}">
<p id="donkdescriptor"><label for=donkdesc>description:</label><br>
<input type="text" name="donkdesc" value="{{ .DonkDesc }}" autocomplete=off>
{{ with .SavedPlace }}
<p><button id=checkinbutton type=button onclick="fillcheckin()">checkin</button>
<p><button id=checkinbutton type=button>checkin</button>
<div id=placedescriptor>
<p><label>name:</label><br><input type="text" name="placename" id=placenameinput value="{{ .Name }}">
<p><label>url:</label><br><input type="text" name="placeurl" id=placeurlinput value="{{ .Url }}">
@ -22,16 +22,16 @@
<label>lon: </label><input type="text" size=9 name="placelong" id=placelonginput value="{{ .Longitude }}">
</div>
{{ else }}
<p><button id=checkinbutton type=button onclick="fillcheckin()">checkin</button>
<div id=placedescriptor style="display: none">
<p><button id=checkinbutton type=button>checkin</button>
<div id=placedescriptor class="hide">
<p><label>name:</label><br><input type="text" name="placename" id=placenameinput value="">
<p><label>url:</label><br><input type="text" name="placeurl" id=placeurlinput value="">
<p><label>lat: </label><input type="text" size=9 name="placelat" id=placelatinput value="">
<label>lon: </label><input type="text" size=9 name="placelong" id=placelonginput value="">
</div>
{{ end }}
<p><button id=addtimebutton type=button onclick="showelement('timedescriptor')">add time</button>
<div id=timedescriptor style="{{ or .ShowTime "display: none" }}">
<p><button id=addtimebutton type=button>add time</button>
<div id=timedescriptor class="{{ or .ShowTime "hide" }}">
<p><label for=timestart>start:</label><br>
<input type="text" name="timestart" value="{{ .StartTime }}">
<p><label for=timeend>duration:</label><br>
@ -43,5 +43,5 @@
<p class="buttonarray">
<button>it's gonna be honked</button>
<button name="preview" value="preview">preview</button>
<button type=button name="cancel" value="cancel" onclick="cancelhonking()">cancel</button>
<button type=button name="cancel" value="cancel">cancel</button>
</form>

View File

@ -3,42 +3,24 @@
<div class="info" id="infobox">
<div id="srvmsg">
{{ if .Name }}
<p>{{ .Name }} <span style="margin-left:1em;"><a href="/u/{{ .Name }}/rss">rss</a></span>
<p>{{ .Name }} <span class="left1em"><a href="/u/{{ .Name }}/rss">rss</a></span>
<p>{{ .WhatAbout }}
{{ end }}
<p>{{ .ServerMessage }}
</div>
{{ if .HonkCSRF }}
{{ template "honkform.html" . }}
<script>
var csrftoken = {{ .HonkCSRF }}
var honksforpage = { }
var curpagestate = { name: "{{ .PageName }}", arg : "{{ .PageArg }}" }
var tophid = { }
tophid[curpagestate.name + ":" + curpagestate.arg] = "{{ .TopHID }}"
var servermsgs = { }
servermsgs[curpagestate.name + ":" + curpagestate.arg] = "{{ .ServerMessage }}"
</script>
<script src="/honkpage.js{{ .JSParam }}"></script>
<script src="/honkpage.js{{ .JSParam }}" defer data-csrf="{{ .HonkCSRF }}" data-pagename="{{ .PageName }}" data-pagearg="{{ .PageArg }}" data-tophid="{{ .TopHID }}" data-srvmsg="{{ .ServerMessage }}"></script>
{{ end }}
<script>
function playit(elem, word, wordlist, xid) {
import('/wonk.js').then(module => {
makeaguess = module.makeaguess
module.addguesscontrols(elem, word, wordlist, xid)
})
}
</script>
{{ if .LocalJSParam }}
<script src="/local.js{{ .LocalJSParam }}"></script>
<script src="/local.js{{ .LocalJSParam }}" defer></script>
{{ end }}
</div>
{{ if and .HonkCSRF (not .IsPreview) }}
<div class="info" id="refreshbox">
<p><button onclick="refreshhonks(this)">refresh</button><span></span>
<button onclick="oldestnewest(this)">scroll down</button>
<p><button class="refresh">refresh</button><span></span>
<button class="scrolldown">scroll down</button>
</div>
{{ if eq .ServerMessage "one honk maybe more" }} <script> hideelement("refreshbox")</script> {{ end }}
{{ end }}
<div id="honksonpage">
<div>

View File

@ -1,3 +1,9 @@
var csrftoken = ""
var honksforpage = { }
var curpagestate = { name: "", arg : "" }
var tophid = { }
var servermsgs = { }
function encode(hash) {
var s = []
for (var key in hash) {
@ -268,6 +274,77 @@ function relinklinks() {
el.onclick = pageswitcher("honker", xid)
el.classList.remove("honkerlink")
}
els = document.querySelectorAll("#honksonpage article button")
els.forEach(function(el) {
var honk = el.closest("article")
var convoy = honk.dataset.convoy
var hname = honk.dataset.hname
var xid = honk.dataset.xid
var id = Number(honk.dataset.id)
if (!(id > 0)) {
console.error("could not determine honk id")
return
}
if (el.classList.contains("unbonk")) {
el.onclick = function() {
unbonk(el, xid);
}
} else if (el.classList.contains("bonk")) {
el.onclick = function() {
bonk(el, xid)
}
} else if (el.classList.contains("honkback")) {
el.onclick = function() {
return showhonkform(el, xid, hname)
}
} else if (el.classList.contains("mute")) {
el.onclick = function() {
muteit(el, convoy);
}
} else if (el.classList.contains("evenmore")) {
var more = document.querySelector("#evenmore"+id);
el.onclick = function() {
more.classList.toggle("hide");
}
} else if (el.classList.contains("zonk")) {
el.onclick = function() {
zonkit(el, xid);
}
} else if (el.classList.contains("flogit-deack")) {
el.onclick = function() {
flogit(el, "deack", xid);
}
} else if (el.classList.contains("flogit-ack")) {
el.onclick = function() {
flogit(el, "ack", xid);
}
} else if (el.classList.contains("flogit-unsave")) {
el.onclick = function() {
flogit(el, "unsave", xid);
}
} else if (el.classList.contains("flogit-save")) {
el.onclick = function() {
flogit(el, "save", xid);
}
} else if (el.classList.contains("flogit-untag")) {
el.onclick = function() {
flogit(el, "untag", xid);
}
} else if (el.classList.contains("flogit-react")) {
el.onclick = function() {
flogit(el, "react", xid);
}
} else if (el.classList.contains("playit")) {
var noise = el.dataset.noise
var wonk = el.dataset.wonk
el.onclick = function() {
playit(el, noise, wonk, xid)
}
}
})
}
(function() {
var el = document.getElementById("homelink")
@ -363,3 +440,56 @@ function fillcheckin() {
}, gpsoptions)
}
}
function playit(elem, word, wordlist, xid) {
import('/wonk.js').then(module => {
makeaguess = module.makeaguess
module.addguesscontrols(elem, word, wordlist, xid)
})
}
// init
(function() {
var me = document.currentScript;
csrftoken = me.dataset.csrf
curpagestate.name = me.dataset.pagename
curpagestate.arg = me.dataset.pagearg
tophid[curpagestate.name + ":" + curpagestate.arg] = me.dataset.tophid
servermsgs[curpagestate.name + ":" + curpagestate.arg] = me.dataset.srvmsg
var totop = document.querySelector(".nophone")
if (totop) {
totop.onclick = function() {
window.scrollTo(0,0)
}
}
var refreshbox = document.getElementById("refreshbox")
if (refreshbox) {
refreshbox.querySelectorAll("button").forEach(function(el) {
if (el.classList.contains("refresh")) {
el.onclick = function() {
refreshhonks(el)
}
} else if (el.classList.contains("scrolldown")) {
el.onclick = function() {
oldestnewest(el)
}
}
})
if (me.dataset.srvmsg == "one honk maybe more") {
hideelement(refreshbox)
}
}
var td = document.getElementById("timedescriptor")
document.getElementById("addtimebutton").onclick = function() {
td.classList.toggle("hide")
}
document.getElementById("honkingtime").onclick = function() {
return showhonkform()
}
document.getElementById("checkinbutton").onclick = fillcheckin
document.querySelector("#donker input").onchange = updatedonker
document.querySelector("button[name=cancel]").onclick = cancelhonking
})();

View File

@ -10,7 +10,7 @@
{{ $letter = (call $firstrune .Name) }}
<li><p>
{{ end }}
<span style="white-space: nowrap;"><a href="/o/{{ .Name }}">#{{ .Name }}</a> ({{ .Count }})</span>
<span class="wsnowrap"><a href="/o/{{ .Name }}">#{{ .Name }}</a> ({{ .Count }})</span>
{{ end }}
</ul>
</div>

View File

@ -340,3 +340,44 @@ img.emu {
--fg-limited: #a79;
}
}
/*
* CSP: style-src: self
*/
li.details {
list-style-type: none;
margin-left: -1em;
}
.left1em {
margin-left: 1em;
}
.hide {
display: none;
}
.textright {
text-align: right;
}
.font08em {
font-size: 0.8em;
}
.font18em {
font-size: 1.8em;
}
.wsnowrap {
white-space: nowrap;
}
.skinny main {
max-width: 700px;
}
.fontmonospace {
font-family: monospace;
}

View File

@ -17,7 +17,10 @@ export function addguesscontrols(elem, word, wordlist, xid) {
}
host.validGuesses = validguesses
var div = document.createElement( 'div' );
div.innerHTML = "<p><input> <button onclick='return makeaguess(this)'>guess</button>"
div.innerHTML = "<p><input> <button>guess</button>"
div.querySelector('button').onclick = function() {
makeaguess(this)
}
host.append(div)
elem.remove()
}
@ -57,7 +60,7 @@ export function makeaguess(btn) {
}
var div = document.createElement( 'div' );
div.innerHTML = "<p style='font-family: monospace'>" + res
div.innerHTML = "<p class='fontmonospace'>" + res
host.append(div)
host.guesses.push(obfu)
} else {
@ -76,7 +79,10 @@ export function makeaguess(btn) {
if (typeof(csrftoken) != "undefined")
post("/zonkit", encode({"CSRF": csrftoken, "wherefore": "wonk", "guesses": host.guesses.join("<p>"), "what": host.xid}))
} else {
div.innerHTML = "<p><input> <button onclick='return makeaguess(this)'>guess</button>"
div.innerHTML = "<p><input> <button>guess</button>"
div.querySelector('button').onclick = function() {
makeaguess(this)
}
}
host.append(div)
btn.parentElement.remove()

22
web.go
View File

@ -50,16 +50,16 @@ var honkSep = "h"
var develMode = false
func getuserstyle(u *login.UserInfo) template.CSS {
func getuserstyle(u *login.UserInfo) template.HTMLAttr {
if u == nil {
return ""
}
user, _ := butwhatabout(u.Username)
css := template.CSS("")
class := template.HTMLAttr("")
if user.Options.SkinnyCSS {
css += "main { max-width: 700px; }\n"
class += `class="skinny"`
}
return css
return class
}
func getmaplink(u *login.UserInfo) string {
@ -80,6 +80,7 @@ func getInfo(r *http.Request) map[string]interface{} {
templinfo["LocalStyleParam"] = getassetparam(dataDir + "/views/local.css")
templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
templinfo["LocalJSParam"] = getassetparam(dataDir + "/views/local.js")
templinfo["MiscJSParam"] = getassetparam(dataDir + "/views/misc.js")
templinfo["ServerName"] = serverName
templinfo["IconName"] = iconName
templinfo["UserSep"] = userSep
@ -1429,7 +1430,7 @@ func edithonkpage(w http.ResponseWriter, r *http.Request) {
templinfo["Noise"] = noise
templinfo["SavedPlace"] = honk.Place
if tm := honk.Time; tm != nil {
templinfo["ShowTime"] = ";"
templinfo["ShowTime"] = " "
templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
if tm.Duration != 0 {
templinfo["Duration"] = tm.Duration
@ -1751,7 +1752,7 @@ func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
templinfo["Noise"] = r.FormValue("noise")
templinfo["SavedFile"] = donkxid
if tm := honk.Time; tm != nil {
templinfo["ShowTime"] = ";"
templinfo["ShowTime"] = " "
templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
if tm.Duration != 0 {
templinfo["Duration"] = tm.Duration
@ -2414,6 +2415,13 @@ func bgmonitor() {
}
}
func addcspheaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; connect-src 'self'; style-src 'self'; img-src 'self'; report-uri /csp-violation")
next.ServeHTTP(w, r)
})
}
func serve() {
db := opendatabase()
login.Init(login.InitArgs{Db: db, Logger: ilog, Insecure: develMode, SameSiteStrict: !develMode})
@ -2466,6 +2474,7 @@ func serve() {
}
mux := mux.NewRouter()
mux.Use(addcspheaders)
mux.Use(login.Checker)
mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
@ -2503,6 +2512,7 @@ func serve() {
getters.HandleFunc("/style.css", serveviewasset)
getters.HandleFunc("/honkpage.js", serveviewasset)
getters.HandleFunc("/wonk.js", serveviewasset)
getters.HandleFunc("/misc.js", serveviewasset)
getters.HandleFunc("/local.css", servedataasset)
getters.HandleFunc("/local.js", servedataasset)
getters.HandleFunc("/icon.png", servedataasset)