summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-11-15 09:10:57 -0800
committerRose Hogenson <rosehogenson@posteo.net>2025-11-15 09:10:57 -0800
commite3c4636772547507448146e04c71de657a5d4105 (patch)
treefcfa9bf93f66940eb72a3c7135ae17c41e1be7f9
parenta14f5d08c264637639d518267a1397e137823fcf (diff)
downloadroseh.moe-e3c4636772547507448146e04c71de657a5d4105.tar.zst
Protect the reverse proxy with my hand-rolled auth
Defense in depth??
-rw-r--r--roseh.moe.go125
-rw-r--r--templates/login.html.template4
2 files changed, 81 insertions, 48 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index 213e44f..2576627 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -373,50 +373,52 @@ func attachCookie(w http.ResponseWriter) error {
return nil
}
-func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
- const csrfTokenLen = 32
- var csrfToken string
- if csrfCookie, err := r.Cookie("csrf-token"); err == nil {
- csrfToken = csrfCookie.Value
- } else {
- buf := make([]byte, csrfTokenLen)
- rand.Read(buf)
- csrfToken = base64.RawURLEncoding.EncodeToString(buf)
- http.SetCookie(w, &http.Cookie{
- Name: "csrf-token",
- Value: csrfToken,
- Path: "/",
- Secure: true,
- HttpOnly: true,
- SameSite: http.SameSiteStrictMode,
- Partitioned: true,
- })
- }
-
+func cookieAuth(w http.ResponseWriter, r *http.Request) bool {
cookie, err := r.Cookie("auth")
if err != nil {
- return csrfToken, false
+ return false
}
authCookie, err := base64.RawURLEncoding.DecodeString(cookie.Value)
if err != nil {
- return csrfToken, false
+ return false
}
msg, ok := verify(authCookie)
if !ok {
- return csrfToken, false
+ return false
}
var t time.Time
if err := t.UnmarshalBinary(msg); err != nil {
- return csrfToken, false
+ return false
}
cookieAge := time.Since(t)
if cookieAge > cookieExpiration {
- return csrfToken, false
+ return false
}
if cookieAge > 24*time.Hour {
attachCookie(w)
}
- return csrfToken, true
+ return true
+}
+
+func reqCSRFToken(w http.ResponseWriter, r *http.Request) string {
+ const csrfTokenLen = 32
+ if csrfCookie, err := r.Cookie("csrf-token"); err == nil {
+ return csrfCookie.Value
+ } else {
+ buf := make([]byte, csrfTokenLen)
+ rand.Read(buf)
+ csrfToken := base64.RawURLEncoding.EncodeToString(buf)
+ http.SetCookie(w, &http.Cookie{
+ Name: "csrf-token",
+ Value: csrfToken,
+ Path: "/",
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ Partitioned: true,
+ })
+ return csrfToken
+ }
}
func checkCSRFToken(r *http.Request) error {
@@ -445,32 +447,50 @@ var (
)
type loginTemplateArgs struct {
- CSRFToken string
- Error bool
+ SelfURL string
+ Redirect string
+ Error bool
}
-func executeLoginTemplate(w io.Writer, csrfToken string) {
- if err := loginTemplate.Execute(w, loginTemplateArgs{CSRFToken: csrfToken}); err != nil {
+func executeLoginTemplate(w io.Writer, redirect string) {
+ redirect += base64.RawURLEncoding.EncodeToString(mac([]byte(redirect)))
+ if err := loginTemplate.Execute(w, loginTemplateArgs{SelfURL: *selfURL, Redirect: redirect}); err != nil {
log.Printf("Warning: login: %s", err)
}
}
-func login(w http.ResponseWriter, r *http.Request) {
- if err := checkCSRFToken(r); err != nil {
- http.Error(w, "bad CSRF token", http.StatusBadRequest)
- return
+func verifyRedirect(redirect string) (string, bool) {
+ base64MacSize := base64.RawURLEncoding.EncodedLen(macSize)
+ if len(redirect) < base64MacSize {
+ return "", false
+ }
+ redirect, macBase64 := redirect[:len(redirect)-base64MacSize], redirect[len(redirect)-base64MacSize:]
+ messageMAC, err := base64.RawURLEncoding.DecodeString(macBase64)
+ if err != nil {
+ return "", false
+ }
+ expectedMAC := mac([]byte(redirect))
+ if !hmac.Equal(messageMAC, expectedMAC) {
+ return "", false
}
+ return redirect, true
+}
+
+func login(w http.ResponseWriter, r *http.Request) {
if !checkPassword(r.FormValue("password")) {
if err := loginTemplate.Execute(w, loginTemplateArgs{
- Error: true,
- CSRFToken: r.FormValue("csrf-token"),
+ Error: true,
}); err != nil {
log.Printf("Warning: login: %s", err)
}
return
}
attachCookie(w)
- http.Redirect(w, r, "/notepad", http.StatusSeeOther)
+ redirect, ok := verifyRedirect(r.FormValue("redirect"))
+ if !ok {
+ redirect = "/"
+ }
+ http.Redirect(w, r, redirect, http.StatusSeeOther)
}
var (
@@ -488,15 +508,14 @@ type notepadTemplateArgs struct {
}
func notepad(w http.ResponseWriter, r *http.Request) {
- csrfToken, ok := cookieAuth(w, r)
- if !ok {
- executeLoginTemplate(w, csrfToken)
+ if !cookieAuth(w, r) {
+ executeLoginTemplate(w, "/notepad")
return
}
notepadContentsMu.Lock()
currentContent := notepadContents
notepadContentsMu.Unlock()
- if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent, CSRFToken: csrfToken}); err != nil {
+ if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent, CSRFToken: reqCSRFToken(w, r)}); err != nil {
log.Printf("Warning: notepad: %s", err)
}
}
@@ -505,7 +524,7 @@ func saveNote(w http.ResponseWriter, r *http.Request) error {
if err := checkCSRFToken(r); err != nil {
return err
}
- if _, ok := cookieAuth(w, r); !ok {
+ if !cookieAuth(w, r) {
return fmt.Errorf("not logged in")
}
newContent := r.FormValue("content")
@@ -561,6 +580,18 @@ func pkg(w http.ResponseWriter, r *http.Request) {
}
}
+type jellyfinReverseProxy struct {
+ proxy httputil.ReverseProxy
+}
+
+func (p *jellyfinReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if !cookieAuth(w, r) {
+ executeLoginTemplate(w, "https://cinema.rose.moe")
+ return
+ }
+ p.proxy.ServeHTTP(w, r)
+}
+
func main() {
flag.Parse()
@@ -587,10 +618,12 @@ func main() {
if err != nil {
log.Fatal(err)
}
- http.Handle("cinema.roseh.moe/", &httputil.ReverseProxy{
- Rewrite: func(r *httputil.ProxyRequest) {
- r.SetURL(jellyfinURL)
- r.SetXForwarded()
+ http.Handle("cinema.roseh.moe/", &jellyfinReverseProxy{
+ proxy: httputil.ReverseProxy{
+ Rewrite: func(r *httputil.ProxyRequest) {
+ r.SetURL(jellyfinURL)
+ r.SetXForwarded()
+ },
},
})
diff --git a/templates/login.html.template b/templates/login.html.template
index f08caf7..368e099 100644
--- a/templates/login.html.template
+++ b/templates/login.html.template
@@ -4,8 +4,8 @@
{{if .Error}}
<p class="login-error">Incorrect password</p>
{{end}}
- <form class="password-form" action="/login" method="post">
- <input type="hidden" name="csrf-token" value="{{.CSRFToken}}">
+ <form class="password-form" action="{{.SelfURL}}/login" method="post">
+ <input type="hidden" name="redirect" value="{{.Redirect}}">
<label class="password-label" for="password">Enter password</label>
<input id="password" class="password" type="password" name="password" autofocus>
</form>