From d297eb565814e1ab3d350b9eefc35a219fb51a88 Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 09:11:43 +0000 Subject: [PATCH 01/11] Use stricter cookie attributes --- service/client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/service/client.go b/service/client.go index e4ab8cb..18ebb52 100644 --- a/service/client.go +++ b/service/client.go @@ -33,9 +33,11 @@ func (c *client) setSession(sess *model.Session) error { return err } http.SetCookie(c.w, &http.Cookie{ - Name: "session", - Value: sb.String(), - Expires: time.Now().Add(365 * 24 * time.Hour), + Name: "session", + Path: "/", + HttpOnly: true, + Value: sb.String(), + Expires: time.Now().Add(365 * 24 * time.Hour), }) return nil } @@ -53,6 +55,7 @@ func (c *client) getSession() (sess *model.Session, err error) { func (c *client) unsetSession() { http.SetCookie(c.w, &http.Cookie{ Name: "session", + Path: "/", Value: "", Expires: time.Now(), }) From c7f40c1e1568bf3f0fcb15c0fc0d01f0f253675d Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 09:19:56 +0000 Subject: [PATCH 02/11] Cleanup oauth redirect URL generation --- service/service.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/service/service.go b/service/service.go index 6b8d0ee..ca80a2b 100644 --- a/service/service.go +++ b/service/service.go @@ -859,6 +859,7 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod if err != nil { return } + rurl = app.AuthURI sess = &model.Session{ ID: sid, Instance: instance, @@ -867,20 +868,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod CSRFToken: csrf, Settings: *model.NewSettings(), } - - u, err := url.Parse("/oauth/authorize") - if err != nil { - return - } - - q := make(url.Values) - q.Set("scope", "read write follow") - q.Set("client_id", app.ClientID) - q.Set("response_type", "code") - q.Set("redirect_uri", s.cwebsite+"/oauth_callback") - u.RawQuery = q.Encode() - - rurl = instanceURL + u.String() return } From 927072e26a470ed3084e36ae2413f4dfd088905f Mon Sep 17 00:00:00 2001 From: r Date: Sat, 7 Oct 2023 10:20:11 +0000 Subject: [PATCH 03/11] Remove unused session ID field --- model/session.go | 1 - service/service.go | 5 ----- util/rand.go | 4 ---- 3 files changed, 10 deletions(-) diff --git a/model/session.go b/model/session.go index 6ada4aa..f9e4287 100644 --- a/model/session.go +++ b/model/session.go @@ -1,7 +1,6 @@ package model type Session struct { - ID string `json:"id,omitempty"` UserID string `json:"uid,omitempty"` Instance string `json:"ins,omitempty"` ClientID string `json:"cid,omitempty"` diff --git a/service/service.go b/service/service.go index ca80a2b..2f87fa3 100644 --- a/service/service.go +++ b/service/service.go @@ -840,10 +840,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod instanceURL = "https://" + instance } - sid, err := util.NewSessionID() - if err != nil { - return - } csrf, err := util.NewCSRFToken() if err != nil { return @@ -861,7 +857,6 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sess *mod } rurl = app.AuthURI sess = &model.Session{ - ID: sid, Instance: instance, ClientID: app.ClientID, ClientSecret: app.ClientSecret, diff --git a/util/rand.go b/util/rand.go index 90e66a5..f1692b9 100644 --- a/util/rand.go +++ b/util/rand.go @@ -16,10 +16,6 @@ func NewRandID(n int) (string, error) { return enc.EncodeToString(data), nil } -func NewSessionID() (string, error) { - return NewRandID(24) -} - func NewCSRFToken() (string, error) { return NewRandID(24) } From ed521dd33d0d002c577a75e349136fed25b7fda5 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 15 Oct 2023 15:46:54 +0000 Subject: [PATCH 04/11] Restrict instance level custom CSS to static directory --- bloat.conf | 1 - main.go | 9 +-------- templates/header.tmpl | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bloat.conf b/bloat.conf index f29e553..9bd2781 100644 --- a/bloat.conf +++ b/bloat.conf @@ -38,5 +38,4 @@ post_formats=PlainText:text/plain,HTML:text/html,Markdown:text/markdown,BBCode:t # single_instance=pl.mydomain.com # Path to custom CSS. Value can be a file path relative to the static directory. -# or a URL starting with either "http://" or "https://". # custom_css=custom.css diff --git a/main.go b/main.go index 657912d..9e9ba4e 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "bloat/config" "bloat/renderer" @@ -47,14 +46,8 @@ func main() { errExit(err) } - customCSS := config.CustomCSS - if len(customCSS) > 0 && !strings.HasPrefix(customCSS, "http://") && - !strings.HasPrefix(customCSS, "https://") { - customCSS = "/static/" + customCSS - } - s := service.NewService(config.ClientName, config.ClientScope, - config.ClientWebsite, customCSS, config.SingleInstance, + config.ClientWebsite, config.CustomCSS, config.SingleInstance, config.PostFormats, renderer) handler := service.NewHandler(s, *verbose, config.StaticDirectory) diff --git a/templates/header.tmpl b/templates/header.tmpl index 8a1b0ca..7ebf65e 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -20,7 +20,7 @@ {{if gt .Count 0}}({{.Count}}){{end}} {{.Title}} {{if .CustomCSS}} - + {{end}} {{if $.Ctx.FluorideMode}} From 67b13c71baea56eeb15532ca1b1377f6da8d18ac Mon Sep 17 00:00:00 2001 From: r Date: Sun, 15 Oct 2023 15:53:44 +0000 Subject: [PATCH 05/11] Use CSP header to restrict resource loading This helps mitigate XSS exploits. Users will have to save the settings again to make the custom CSS work. --- model/session.go | 2 ++ service/service.go | 16 ++++++++++++++-- service/transport.go | 25 +++++++++++++++++++++---- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/model/session.go b/model/session.go index f9e4287..61a409c 100644 --- a/model/session.go +++ b/model/session.go @@ -27,6 +27,7 @@ type Settings struct { AntiDopamineMode bool `json:"adm,omitempty"` HideUnsupportedNotifs bool `json:"hun,omitempty"` CSS string `json:"css,omitempty"` + CSSHash string `json:"cssh,omitempty"` } func NewSettings() *Settings { @@ -43,5 +44,6 @@ func NewSettings() *Settings { AntiDopamineMode: false, HideUnsupportedNotifs: false, CSS: "", + CSSHash: "", } } diff --git a/service/service.go b/service/service.go index 2f87fa3..c925b83 100644 --- a/service/service.go +++ b/service/service.go @@ -1,6 +1,8 @@ package service import ( + "crypto/sha256" + "encoding/base64" "errors" "fmt" "mime/multipart" @@ -1014,8 +1016,18 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error) default: return errInvalidArgument } - if len(settings.CSS) > 1<<20 { - return errInvalidArgument + if len(settings.CSS) > 0 { + if len(settings.CSS) > 1<<20 { + return errInvalidArgument + } + // For some reason, browsers convert CRLF to LF before calculating + // the hash of the inline resources. + settings.CSS = strings.ReplaceAll(settings.CSS, "\x0d\x0a", "\x0a") + + h := sha256.Sum256([]byte(settings.CSS)) + settings.CSSHash = base64.StdEncoding.EncodeToString(h[:]) + } else { + settings.CSSHash = "" } c.s.Settings = *settings return c.setSession(c.s) diff --git a/service/transport.go b/service/transport.go index 1182d6c..d032cce 100644 --- a/service/transport.go +++ b/service/transport.go @@ -26,6 +26,16 @@ const ( CSRF ) +const csp = "default-src 'none';" + + " img-src *;" + + " media-src *;" + + " font-src *;" + + " child-src *;" + + " connect-src 'self';" + + " form-action 'self';" + + " script-src 'self';" + + " style-src 'self'" + func NewHandler(s *service, verbose bool, staticDir string) http.Handler { r := mux.NewRouter() @@ -58,14 +68,14 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { }(time.Now()) } - var ct string + h := c.w.Header() switch rt { case HTML: - ct = "text/html; charset=utf-8" + h.Set("Content-Type", "text/html; charset=utf-8") + h.Set("Content-Security-Policy", csp) case JSON: - ct = "application/json" + h.Set("Content-Type", "application/json") } - c.w.Header().Add("Content-Type", ct) err = c.authenticate(at, s.instance) if err != nil { @@ -73,6 +83,13 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { return } + // Override the CSP header to allow custom CSS + if rt == HTML && len(c.s.Settings.CSS) > 0 && + len(c.s.Settings.CSSHash) > 0 { + v := fmt.Sprintf("%s 'sha256-%s'", csp, c.s.Settings.CSSHash) + h.Set("Content-Security-Policy", v) + } + err = f(c) if err != nil { writeError(c, err, rt, req.Method == http.MethodGet) From 9b053e32ec8a42c74f3c09d28a8df4086b5b2945 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 22 Oct 2023 11:11:21 +0000 Subject: [PATCH 06/11] Fix replace syntax --- service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/service.go b/service/service.go index c925b83..db309ff 100644 --- a/service/service.go +++ b/service/service.go @@ -1022,7 +1022,7 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error) } // For some reason, browsers convert CRLF to LF before calculating // the hash of the inline resources. - settings.CSS = strings.ReplaceAll(settings.CSS, "\x0d\x0a", "\x0a") + settings.CSS = strings.Replace(settings.CSS, "\x0d\x0a", "\x0a", -1) h := sha256.Sum256([]byte(settings.CSS)) settings.CSSHash = base64.StdEncoding.EncodeToString(h[:]) From 597cfc6b1ed23dc85774a43055416c98b77cae67 Mon Sep 17 00:00:00 2001 From: r Date: Sun, 22 Oct 2023 11:12:27 +0000 Subject: [PATCH 07/11] fluoride: Add image preview for profile image --- static/fluoride.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/fluoride.js b/static/fluoride.js index abeed21..d5c5b5d 100644 --- a/static/fluoride.js +++ b/static/fluoride.js @@ -325,7 +325,7 @@ document.addEventListener("DOMContentLoaded", function() { links[j].target = "_blank"; } - var links = document.querySelectorAll(".status-media-container .img-link"); + var links = document.querySelectorAll(".status-media-container .img-link, .user-profile-img-container .img-link"); for (var j = 0; j < links.length; j++) { handleImgPreview(links[j]); } From f4881e72675e87a9eae716436c3ac18a788d596d Mon Sep 17 00:00:00 2001 From: r Date: Wed, 25 Oct 2023 06:40:34 +0000 Subject: [PATCH 08/11] Remove form-action CSP directive Chrome incorrectly restricts the redirect URL to the sources specified in the form-action value, which prevents the instance oauth page from loading. --- service/transport.go | 1 - 1 file changed, 1 deletion(-) diff --git a/service/transport.go b/service/transport.go index d032cce..f7e31d6 100644 --- a/service/transport.go +++ b/service/transport.go @@ -32,7 +32,6 @@ const csp = "default-src 'none';" + " font-src *;" + " child-src *;" + " connect-src 'self';" + - " form-action 'self';" + " script-src 'self';" + " style-src 'self'" From 53dd0c50ef823326f19311ca6a48d886e3636e66 Mon Sep 17 00:00:00 2001 From: localhost_frssoft Date: Mon, 6 Nov 2023 12:17:09 +0300 Subject: [PATCH 09/11] removed session id from register --- service/service.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/service/service.go b/service/service.go index 7c9d704..e8d0821 100644 --- a/service/service.go +++ b/service/service.go @@ -991,10 +991,6 @@ func (s *service) NewSessionRegister(c *client, instance string, reason string, instanceURL = "https://" + instance } - sid, err := util.NewSessionID() - if err != nil { - return - } csrf, err := util.NewCSRFToken() if err != nil { return @@ -1021,7 +1017,6 @@ func (s *service) NewSessionRegister(c *client, instance string, reason string, return } sess = &model.Session{ - ID: sid, Instance: instance, UserID: "1", ClientID: app.ClientID, From 1c3cb0f3585ad76c0e8f0c03998637ebbcf57678 Mon Sep 17 00:00:00 2001 From: localhost_frssoft Date: Mon, 6 Nov 2023 12:46:18 +0300 Subject: [PATCH 10/11] footer sign-in changed --- templates/signin.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/signin.tmpl b/templates/signin.tmpl index 9dcec63..af46fe8 100644 --- a/templates/signin.tmpl +++ b/templates/signin.tmpl @@ -53,7 +53,7 @@ git.freesoftwareextremist.com/bloat for more details.
- bloat-gts fork branch + localhost_custom fork branch

{{template "footer.tmpl"}} From 6b3240dd9a5a853cd110347127864c7fd6c9ec73 Mon Sep 17 00:00:00 2001 From: localhost_frssoft Date: Mon, 6 Nov 2023 13:11:16 +0300 Subject: [PATCH 11/11] remove unusable signup feature --- mastodon/apps.go | 47 --------------------------------- service/service.go | 60 ------------------------------------------- service/transport.go | 27 ------------------- templates/signin.tmpl | 35 ------------------------- 4 files changed, 169 deletions(-) diff --git a/mastodon/apps.go b/mastodon/apps.go index 1ce894f..652be6e 100644 --- a/mastodon/apps.go +++ b/mastodon/apps.go @@ -92,50 +92,3 @@ func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error return &app, nil } - -type AppAuth struct { - http.Client -} - -// RegisterApp make auth application and return app token. -func AuthApp(ctx context.Context, appConfig *Application, instance string) (*string, error) { - var appAuth AppAuth - params := url.Values{} - params.Set("client_id", appConfig.ClientID) - params.Set("client_secret", appConfig.ClientSecret) - params.Set("redirect_uris", "urn:ietf:wg:oauth:2.0:oob") - params.Set("grant_type", "client_credentials") - params.Set("scope", "read write follow") - - u, err := url.Parse("https://" + instance) - if err != nil { - return nil, err - } - u.Path = path.Join(u.Path, "/oauth/token") - - req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode())) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := appAuth.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, parseAPIError("bad request", resp) - } - - var res struct { - AccessToken string `json:"access_token"` - } - err = json.NewDecoder(resp.Body).Decode(&res) - if err != nil { - return nil, err - } - - return &res.AccessToken, nil -} diff --git a/service/service.go b/service/service.go index e8d0821..7ac5624 100644 --- a/service/service.go +++ b/service/service.go @@ -982,66 +982,6 @@ func (s *service) Signin(c *client, code string) (err error) { return c.setSession(c.s) } -func (s *service) NewSessionRegister(c *client, instance string, reason string, username string, email string, password string, agreement bool, locale string, registerCredintals mastodon.RegisterCredintals) (rurl string, sess *model.Session, err error) { - var instanceURL string - if strings.HasPrefix(instance, "https://") { - instanceURL = instance - instance = strings.TrimPrefix(instance, "https://") - } else { - instanceURL = "https://" + instance - } - - csrf, err := util.NewCSRFToken() - if err != nil { - return - } - - app, err := mastodon.RegisterApp(c.ctx, &mastodon.AppConfig{ - Server: instanceURL, - ClientName: s.cname, - Scopes: s.cscope, - Website: s.cwebsite, - RedirectURIs: s.cwebsite + "/oauth_callback", - }) - if err != nil { - return - } - registerCredintals.App = app - bearer, err := mastodon.AuthApp(c.ctx, app, instance) - if err != nil { - return - } - token, err := mastodon.RegisterAccount(c.ctx, instance, reason, username, email, password, agreement, locale, registerCredintals, *bearer) - - if err != nil { - return - } - sess = &model.Session{ - Instance: instance, - UserID: "1", - ClientID: app.ClientID, - ClientSecret: app.ClientSecret, - AccessToken: *token, - CSRFToken: csrf, - Settings: *model.NewSettings(), - } - - u, err := url.Parse("/oauth/authorize") - if err != nil { - return - } - - q := make(url.Values) - q.Set("scope", "read write follow") - q.Set("client_id", app.ClientID) - q.Set("response_type", "code") - q.Set("redirect_uri", s.cwebsite+"/oauth_callback") - u.RawQuery = q.Encode() - - rurl = instanceURL + u.String() - return -} - func (s *service) Signout(c *client) (err error) { return c.RevokeToken(c.ctx) } diff --git a/service/transport.go b/service/transport.go index 2984bd7..2b579e4 100644 --- a/service/transport.go +++ b/service/transport.go @@ -293,32 +293,6 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { return nil }, NOAUTH, HTML) - signup := handle(func(c *client) error { - instance := c.r.FormValue("instanceup") - reason := c.r.FormValue("reason") - username := c.r.FormValue("username") - email := c.r.FormValue("email") - password := c.r.FormValue("password") - agreement := c.r.FormValue("agreement") == "true" - locale := c.r.FormValue("locale") - url, sess, err := s.NewSessionRegister(c, instance, reason, username, email, password, agreement, locale, mastodon.RegisterCredintals{ - Server: "https://"+instance, - Reason: reason, - Username: username, - Email: email, - Password: password, - Agreement: agreement, - Locale: locale, - }) - if err != nil { - return err - } - c.setSession(sess) - url = "/confirmation" - c.redirect(url) - return nil - }, NOAUTH, HTML) - oauthCallback := handle(func(c *client) error { q := c.r.URL.Query() token := q.Get("code") @@ -856,7 +830,6 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { r.HandleFunc("/profile/delavatar", profileDelAvatar).Methods(http.MethodPost) r.HandleFunc("/profile/delbanner", profileDelBanner).Methods(http.MethodPost) r.HandleFunc("/signin", signin).Methods(http.MethodPost) - r.HandleFunc("/signup", signup).Methods(http.MethodPost) r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet) r.HandleFunc("/post", post).Methods(http.MethodPost) r.HandleFunc("/like/{id}", like).Methods(http.MethodPost) diff --git a/templates/signin.tmpl b/templates/signin.tmpl index af46fe8..7726508 100644 --- a/templates/signin.tmpl +++ b/templates/signin.tmpl @@ -13,41 +13,6 @@ -
-Sign up -

-

- -

-
-

See git.freesoftwareextremist.com/bloat