diff --git a/bloat.conf b/bloat.conf index e583988..20d5fef 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/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/model/session.go b/model/session.go index 7c4ff27..cb634ee 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"` @@ -30,6 +29,7 @@ type Settings struct { InstanceEmojiFilter string `json:"iemojfilter,omitempty"` AddReactionsFilter string `json:"reactionfilter,omitempty"` CSS string `json:"css,omitempty"` + CSSHash string `json:"cssh,omitempty"` } func NewSettings() *Settings { @@ -48,5 +48,6 @@ func NewSettings() *Settings { InstanceEmojiFilter: "", AddReactionsFilter: "", CSS: "", + CSSHash: "", } } diff --git a/service/client.go b/service/client.go index 7c5c33c..49bc145 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(), }) diff --git a/service/service.go b/service/service.go index 6870f09..7ac5624 100644 --- a/service/service.go +++ b/service/service.go @@ -1,6 +1,8 @@ package service import ( + "crypto/sha256" + "encoding/base64" "errors" "fmt" "mime/multipart" @@ -936,10 +938,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 @@ -955,28 +953,14 @@ 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, ClientID: app.ClientID, ClientSecret: app.ClientSecret, 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 } @@ -998,71 +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 - } - - sid, err := util.NewSessionID() - if err != nil { - return - } - 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{ - ID: sid, - 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) } @@ -1213,8 +1132,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.Replace(settings.CSS, "\x0d\x0a", "\x0a", -1) + + 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 c5bdde7..2b579e4 100644 --- a/service/transport.go +++ b/service/transport.go @@ -26,6 +26,15 @@ const ( CSRF ) +const csp = "default-src 'none';" + + " img-src *;" + + " media-src *;" + + " font-src *;" + + " child-src *;" + + " connect-src 'self';" + + " script-src 'self';" + + " style-src 'self'" + func NewHandler(s *service, verbose bool, staticDir string) http.Handler { r := mux.NewRouter() @@ -58,14 +67,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 +82,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) @@ -277,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") @@ -840,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/static/fluoride.js b/static/fluoride.js index ee3e3d8..70379de 100644 --- a/static/fluoride.js +++ b/static/fluoride.js @@ -326,7 +326,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]); } 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}} diff --git a/templates/signin.tmpl b/templates/signin.tmpl index 9dcec63..7726508 100644 --- a/templates/signin.tmpl +++ b/templates/signin.tmpl @@ -13,47 +13,12 @@ -
-Sign up -

-

- Enter the domain name of your instance to continue -
- -
- Enter the reason why you want register -
- -
- The desired username for the account -
- -
- The email address to be used for login -
- -
- The password to be used for login (Please use strong password!) -
- -
- You agrees to the terms, conditions, and policies of the instance -
- - -
- -
- -

-
-

See git.freesoftwareextremist.com/bloat for more details.
- bloat-gts fork branch + localhost_custom fork branch

{{template "footer.tmpl"}} 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) }