diff options
| -rw-r--r-- | roseh.moe.go | 125 | ||||
| -rw-r--r-- | templates/login.html.template | 4 |
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> |
