aboutsummaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
Diffstat (limited to 'service')
-rw-r--r--service/client.go118
-rw-r--r--service/service.go266
-rw-r--r--service/transport.go253
3 files changed, 392 insertions, 245 deletions
diff --git a/service/client.go b/service/client.go
new file mode 100644
index 0000000..18ebb52
--- /dev/null
+++ b/service/client.go
@@ -0,0 +1,118 @@
+package service
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "bloat/mastodon"
+ "bloat/model"
+ "bloat/renderer"
+)
+
+type client struct {
+ *mastodon.Client
+ w http.ResponseWriter
+ r *http.Request
+ s *model.Session
+ csrf string
+ ctx context.Context
+ rctx *renderer.Context
+}
+
+func (c *client) setSession(sess *model.Session) error {
+ var sb strings.Builder
+ bw := base64.NewEncoder(base64.URLEncoding, &sb)
+ err := json.NewEncoder(bw).Encode(sess)
+ bw.Close()
+ if err != nil {
+ return err
+ }
+ http.SetCookie(c.w, &http.Cookie{
+ Name: "session",
+ Path: "/",
+ HttpOnly: true,
+ Value: sb.String(),
+ Expires: time.Now().Add(365 * 24 * time.Hour),
+ })
+ return nil
+}
+
+func (c *client) getSession() (sess *model.Session, err error) {
+ cookie, _ := c.r.Cookie("session")
+ if cookie == nil {
+ return nil, errInvalidSession
+ }
+ br := base64.NewDecoder(base64.URLEncoding, strings.NewReader(cookie.Value))
+ err = json.NewDecoder(br).Decode(&sess)
+ return
+}
+
+func (c *client) unsetSession() {
+ http.SetCookie(c.w, &http.Cookie{
+ Name: "session",
+ Path: "/",
+ Value: "",
+ Expires: time.Now(),
+ })
+}
+
+func (c *client) writeJson(data interface{}) error {
+ return json.NewEncoder(c.w).Encode(map[string]interface{}{
+ "data": data,
+ })
+}
+
+func (c *client) redirect(url string) {
+ c.w.Header().Add("Location", url)
+ c.w.WriteHeader(http.StatusFound)
+}
+
+func (c *client) authenticate(t int, instance string) (err error) {
+ csrf := c.r.FormValue("csrf_token")
+ ref := c.r.URL.RequestURI()
+ defer func() {
+ if c.s == nil {
+ c.s = &model.Session{
+ Settings: *model.NewSettings(),
+ }
+ }
+ c.rctx = &renderer.Context{
+ HideAttachments: c.s.Settings.HideAttachments,
+ MaskNSFW: c.s.Settings.MaskNSFW,
+ ThreadInNewTab: c.s.Settings.ThreadInNewTab,
+ FluorideMode: c.s.Settings.FluorideMode,
+ DarkMode: c.s.Settings.DarkMode,
+ CSRFToken: c.s.CSRFToken,
+ UserID: c.s.UserID,
+ AntiDopamineMode: c.s.Settings.AntiDopamineMode,
+ UserCSS: c.s.Settings.CSS,
+ Referrer: ref,
+ }
+ }()
+ if t < SESSION {
+ return
+ }
+ sess, err := c.getSession()
+ if err != nil {
+ return err
+ }
+ c.s = sess
+ if len(instance) > 0 && c.s.Instance != instance {
+ return errors.New("invalid instance")
+ }
+ c.Client = mastodon.NewClient(&mastodon.Config{
+ Server: "https://" + c.s.Instance,
+ ClientID: c.s.ClientID,
+ ClientSecret: c.s.ClientSecret,
+ AccessToken: c.s.AccessToken,
+ })
+ if t >= CSRF && (len(csrf) < 1 || csrf != c.s.CSRFToken) {
+ return errInvalidCSRFToken
+ }
+ return
+}
diff --git a/service/service.go b/service/service.go
index cda42f8..24e3f85 100644
--- a/service/service.go
+++ b/service/service.go
@@ -1,6 +1,8 @@
package service
import (
+ "crypto/sha256"
+ "encoding/base64"
"errors"
"fmt"
"mime/multipart"
@@ -27,14 +29,11 @@ type service struct {
instance string
postFormats []model.PostFormat
renderer renderer.Renderer
- sessionRepo model.SessionRepo
- appRepo model.AppRepo
}
func NewService(cname string, cscope string, cwebsite string,
css string, instance string, postFormats []model.PostFormat,
- renderer renderer.Renderer, sessionRepo model.SessionRepo,
- appRepo model.AppRepo) *service {
+ renderer renderer.Renderer) *service {
return &service{
cname: cname,
cscope: cscope,
@@ -43,61 +42,18 @@ func NewService(cname string, cscope string, cwebsite string,
instance: instance,
postFormats: postFormats,
renderer: renderer,
- sessionRepo: sessionRepo,
- appRepo: appRepo,
}
}
-func (s *service) authenticate(c *client, sid string, csrf string, ref string, t int) (err error) {
- var sett *model.Settings
- defer func() {
- if sett == nil {
- sett = model.NewSettings()
- }
- c.rctx = &renderer.Context{
- HideAttachments: sett.HideAttachments,
- MaskNSFW: sett.MaskNSFW,
- ThreadInNewTab: sett.ThreadInNewTab,
- FluorideMode: sett.FluorideMode,
- DarkMode: sett.DarkMode,
- CSRFToken: c.s.CSRFToken,
- UserID: c.s.UserID,
- AntiDopamineMode: sett.AntiDopamineMode,
- UserCSS: sett.CSS,
- Referrer: ref,
- }
- }()
- if t < SESSION {
- return
- }
- if len(sid) < 1 {
- return errInvalidSession
- }
- c.s, err = s.sessionRepo.Get(sid)
- if err != nil {
- return errInvalidSession
- }
- sett = &c.s.Settings
- app, err := s.appRepo.Get(c.s.InstanceDomain)
- if err != nil {
- return err
- }
- c.Client = mastodon.NewClient(&mastodon.Config{
- Server: app.InstanceURL,
- ClientID: app.ClientID,
- ClientSecret: app.ClientSecret,
- AccessToken: c.s.AccessToken,
- })
- if t >= CSRF && (len(csrf) < 1 || csrf != c.s.CSRFToken) {
- return errInvalidCSRFToken
- }
- return
-}
-
func (s *service) cdata(c *client, title string, count int, rinterval int,
target string) (data *renderer.CommonData) {
+ if title == "" {
+ title = s.cname
+ } else {
+ title += " - " + s.cname
+ }
data = &renderer.CommonData{
- Title: title + " - " + s.cname,
+ Title: title,
CustomCSS: s.css,
Count: count,
RefreshInterval: rinterval,
@@ -105,6 +61,7 @@ func (s *service) cdata(c *client, title string, count int, rinterval int,
}
if c != nil && c.s.IsLoggedIn() {
data.CSRFToken = c.s.CSRFToken
+ data.Title += " - " + c.s.Instance
}
return
}
@@ -130,7 +87,7 @@ func (s *service) ErrorPage(c *client, err error, retry bool) error {
}
func (s *service) SigninPage(c *client) (err error) {
- cdata := s.cdata(nil, "signin", 0, 0, "")
+ cdata := s.cdata(nil, "Signin", 0, 0, "")
data := &renderer.SigninData{
CommonData: cdata,
}
@@ -138,8 +95,9 @@ func (s *service) SigninPage(c *client) (err error) {
}
func (s *service) RootPage(c *client) (err error) {
+ cdata := s.cdata(c, "", 0, 0, "")
data := &renderer.RootData{
- Title: s.cname,
+ CommonData: cdata,
}
return s.renderer.Render(c.rctx, c.w, renderer.RootPage, data)
}
@@ -154,7 +112,7 @@ func (s *service) NavPage(c *client) (err error) {
DefaultFormat: c.s.Settings.DefaultFormat,
Formats: s.postFormats,
}
- cdata := s.cdata(c, "nav", 0, 0, "main")
+ cdata := s.cdata(c, "Nav", 0, 0, "main")
data := &renderer.NavData{
User: u,
CommonData: cdata,
@@ -251,7 +209,7 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
nextLink = "/timeline/" + tType + "?" + v.Encode()
}
- cdata := s.cdata(c, tType+" timeline ", 0, 0, "")
+ cdata := s.cdata(c, title, 0, 0, "")
data := &renderer.TimelineData{
Title: title,
Type: tType,
@@ -404,7 +362,7 @@ func (s *service) ThreadPage(c *client, id string, reply bool) (err error) {
addToReplyMap(replies, statuses[i].InReplyToID, statuses[i].ID, i+1)
}
- cdata := s.cdata(c, "post by "+status.Account.DisplayName, 0, 0, "")
+ cdata := s.cdata(c, "Post by "+status.Account.DisplayName, 0, 0, "")
data := &renderer.ThreadData{
Statuses: statuses,
PostContext: pctx,
@@ -460,7 +418,7 @@ func (s *service) QuickReplyPage(c *client, id string) (err error) {
},
}
- cdata := s.cdata(c, "post by "+status.Account.DisplayName, 0, 0, "")
+ cdata := s.cdata(c, "Post by "+status.Account.DisplayName, 0, 0, "")
data := &renderer.QuickReplyData{
Ancestor: ancestor,
Status: status,
@@ -475,7 +433,7 @@ func (s *service) LikedByPage(c *client, id string) (err error) {
if err != nil {
return
}
- cdata := s.cdata(c, "likes", 0, 0, "")
+ cdata := s.cdata(c, "Likes", 0, 0, "")
data := &renderer.LikedByData{
CommonData: cdata,
Users: likers,
@@ -488,7 +446,7 @@ func (s *service) RetweetedByPage(c *client, id string) (err error) {
if err != nil {
return
}
- cdata := s.cdata(c, "retweets", 0, 0, "")
+ cdata := s.cdata(c, "Retweets", 0, 0, "")
data := &renderer.RetweetedByData{
CommonData: cdata,
Users: retweeters,
@@ -537,7 +495,7 @@ func (s *service) NotificationPage(c *client, maxID string,
nextLink = "/notifications?max_id=" + pg.MaxID
}
- cdata := s.cdata(c, "notifications", unreadCount,
+ cdata := s.cdata(c, "Notifications", unreadCount,
c.s.Settings.NotificationInterval, "main")
data := &renderer.NotificationData{
Notifications: notifications,
@@ -560,12 +518,19 @@ func (s *service) UserPage(c *client, id string, pageType string,
MinID: minID,
Limit: 20,
}
+ isCurrent := c.s.UserID == id
- user, err := c.GetAccount(c.ctx, id)
+ // Some fields like AccountSource are only available in the
+ // CurrentUser API
+ var user *mastodon.Account
+ if isCurrent {
+ user, err = c.GetAccountCurrentUser(c.ctx)
+ } else {
+ user, err = c.GetAccount(c.ctx, id)
+ }
if err != nil {
return
}
- isCurrent := c.s.UserID == user.ID
switch pageType {
case "":
@@ -677,7 +642,6 @@ func (s *service) UserPage(c *client, id string, pageType string,
cdata := s.cdata(c, user.DisplayName+" @"+user.Acct, 0, 0, "")
data := &renderer.UserData{
User: user,
- IsCurrent: isCurrent,
Type: pageType,
Users: users,
Statuses: statuses,
@@ -691,7 +655,7 @@ func (s *service) UserSearchPage(c *client,
id string, q string, offset int) (err error) {
var nextLink string
- var title = "search"
+ var title = "Search"
user, err := c.GetAccount(c.ctx, id)
if err != nil {
@@ -729,8 +693,21 @@ func (s *service) UserSearchPage(c *client,
return s.renderer.Render(c.rctx, c.w, renderer.UserSearchPage, data)
}
+func (s *service) MutePage(c *client, id string) (err error) {
+ user, err := c.GetAccount(c.ctx, id)
+ if err != nil {
+ return
+ }
+ cdata := s.cdata(c, "Mute "+user.DisplayName+" @"+user.Acct, 0, 0, "")
+ data := &renderer.UserData{
+ User: user,
+ CommonData: cdata,
+ }
+ return s.renderer.Render(c.rctx, c.w, renderer.MutePage, data)
+}
+
func (s *service) AboutPage(c *client) (err error) {
- cdata := s.cdata(c, "about", 0, 0, "")
+ cdata := s.cdata(c, "About", 0, 0, "")
data := &renderer.AboutData{
CommonData: cdata,
}
@@ -742,7 +719,7 @@ func (s *service) EmojiPage(c *client) (err error) {
if err != nil {
return
}
- cdata := s.cdata(c, "emojis", 0, 0, "")
+ cdata := s.cdata(c, "Emojis", 0, 0, "")
data := &renderer.EmojiData{
Emojis: emojis,
CommonData: cdata,
@@ -754,7 +731,7 @@ func (s *service) SearchPage(c *client,
q string, qType string, offset int) (err error) {
var nextLink string
- var title = "search"
+ var title = "Search"
var results *mastodon.Results
if len(q) > 0 {
@@ -790,7 +767,7 @@ func (s *service) SearchPage(c *client,
}
func (s *service) SettingsPage(c *client) (err error) {
- cdata := s.cdata(c, "settings", 0, 0, "")
+ cdata := s.cdata(c, "Settings", 0, 0, "")
data := &renderer.SettingsData{
CommonData: cdata,
Settings: &c.s.Settings,
@@ -804,7 +781,7 @@ func (svc *service) FiltersPage(c *client) (err error) {
if err != nil {
return
}
- cdata := svc.cdata(c, "filters", 0, 0, "")
+ cdata := svc.cdata(c, "Filters", 0, 0, "")
data := &renderer.FiltersData{
CommonData: cdata,
Filters: filters,
@@ -812,6 +789,55 @@ func (svc *service) FiltersPage(c *client) (err error) {
return svc.renderer.Render(c.rctx, c.w, renderer.FiltersPage, data)
}
+func (svc *service) ProfilePage(c *client) (err error) {
+ u, err := c.GetAccountCurrentUser(c.ctx)
+ if err != nil {
+ return
+ }
+ // Some instances allow more than 4 fields, but make sure that there are
+ // at least 4 fields in the slice because the template depends on it.
+ if u.Source.Fields == nil {
+ u.Source.Fields = new([]mastodon.Field)
+ }
+ for len(*u.Source.Fields) < 4 {
+ *u.Source.Fields = append(*u.Source.Fields, mastodon.Field{})
+ }
+ cdata := svc.cdata(c, "Edit profile", 0, 0, "")
+ data := &renderer.ProfileData{
+ CommonData: cdata,
+ User: u,
+ }
+ return svc.renderer.Render(c.rctx, c.w, renderer.ProfilePage, data)
+}
+
+func (s *service) ProfileUpdate(c *client, name, bio string, avatar, banner *multipart.FileHeader,
+ fields []mastodon.Field, locked bool) (err error) {
+ // Need to pass empty data to clear fields
+ if len(fields) == 0 {
+ fields = append(fields, mastodon.Field{})
+ }
+ p := &mastodon.Profile{
+ DisplayName: &name,
+ Note: &bio,
+ Avatar: avatar,
+ Header: banner,
+ Fields: &fields,
+ Locked: &locked,
+ }
+ _, err = c.AccountUpdate(c.ctx, p)
+ return err
+}
+
+func (s *service) ProfileDelAvatar(c *client) (err error) {
+ _, err = c.AccountDeleteAvatar(c.ctx)
+ return
+}
+
+func (s *service) ProfileDelBanner(c *client) (err error) {
+ _, err = c.AccountDeleteHeader(c.ctx)
+ return err
+}
+
func (s *service) SingleInstance() (instance string, ok bool) {
if len(s.instance) > 0 {
instance = s.instance
@@ -820,7 +846,7 @@ func (s *service) SingleInstance() (instance string, ok bool) {
return
}
-func (s *service) NewSession(c *client, instance string) (rurl string, sid string, err error) {
+func (s *service) NewSession(c *client, instance string) (rurl string, sess *model.Session, err error) {
var instanceURL string
if strings.HasPrefix(instance, "https://") {
instanceURL = instance
@@ -829,66 +855,29 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sid strin
instanceURL = "https://" + instance
}
- sid, err = util.NewSessionID()
- if err != nil {
- return
- }
csrf, err := util.NewCSRFToken()
if err != nil {
return
}
- sess := model.Session{
- ID: sid,
- InstanceDomain: instance,
- CSRFToken: csrf,
- Settings: *model.NewSettings(),
- }
- err = s.sessionRepo.Add(sess)
+ 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
}
-
- app, err := s.appRepo.Get(instance)
- if err != nil {
- if err != model.ErrAppNotFound {
- return
- }
- mastoApp, 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 "", "", err
- }
- app = model.App{
- InstanceDomain: instance,
- InstanceURL: instanceURL,
- ClientID: mastoApp.ClientID,
- ClientSecret: mastoApp.ClientSecret,
- }
- err = s.appRepo.Add(app)
- if err != nil {
- return "", "", err
- }
- }
-
- u, err := url.Parse("/oauth/authorize")
- if err != nil {
- return
+ rurl = app.AuthURI
+ sess = &model.Session{
+ Instance: instance,
+ ClientID: app.ClientID,
+ ClientSecret: app.ClientSecret,
+ CSRFToken: csrf,
+ Settings: *model.NewSettings(),
}
-
- 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
}
@@ -907,12 +896,11 @@ func (s *service) Signin(c *client, code string) (err error) {
}
c.s.AccessToken = c.GetAccessToken(c.ctx)
c.s.UserID = u.ID
- return s.sessionRepo.Add(c.s)
+ return c.setSession(c.s)
}
func (s *service) Signout(c *client) (err error) {
- s.sessionRepo.Remove(c.s.ID)
- return
+ return c.RevokeToken(c.ctx)
}
func (s *service) Post(c *client, content string, replyToID string,
@@ -1005,8 +993,8 @@ func (s *service) Reject(c *client, id string) (err error) {
return c.FollowRequestReject(c.ctx, id)
}
-func (s *service) Mute(c *client, id string, notifications *bool) (err error) {
- _, err = c.AccountMute(c.ctx, id, notifications)
+func (s *service) Mute(c *client, id string, notifications bool, duration int) (err error) {
+ _, err = c.AccountMute(c.ctx, id, notifications, duration)
return
}
@@ -1041,15 +1029,21 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error)
default:
return errInvalidArgument
}
- if len(settings.CSS) > 1<<20 {
- return errInvalidArgument
- }
- sess, err := s.sessionRepo.Get(c.s.ID)
- if err != nil {
- return
+ 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 = ""
}
- sess.Settings = *settings
- return s.sessionRepo.Add(sess)
+ c.s.Settings = *settings
+ return c.setSession(c.s)
}
func (s *service) MuteConversation(c *client, id string) (err error) {
diff --git a/service/transport.go b/service/transport.go
index 4518b1a..f7e31d6 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -1,25 +1,21 @@
package service
import (
- "context"
"encoding/json"
+ "fmt"
"log"
+ "mime/multipart"
"net/http"
"strconv"
"time"
"bloat/mastodon"
"bloat/model"
- "bloat/renderer"
"github.com/gorilla/mux"
)
const (
- sessionExp = 365 * 24 * time.Hour
-)
-
-const (
HTML int = iota
JSON
)
@@ -30,36 +26,16 @@ const (
CSRF
)
-type client struct {
- *mastodon.Client
- w http.ResponseWriter
- r *http.Request
- s model.Session
- csrf string
- ctx context.Context
- rctx *renderer.Context
-}
-
-func setSessionCookie(w http.ResponseWriter, sid string, exp time.Duration) {
- http.SetCookie(w, &http.Cookie{
- Name: "session_id",
- Value: sid,
- Expires: time.Now().Add(exp),
- })
-}
-
-func writeJson(c *client, data interface{}) error {
- return json.NewEncoder(c.w).Encode(map[string]interface{}{
- "data": data,
- })
-}
-
-func redirect(c *client, url string) {
- c.w.Header().Add("Location", url)
- c.w.WriteHeader(http.StatusFound)
-}
+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, logger *log.Logger, staticDir string) http.Handler {
+func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
r := mux.NewRouter()
writeError := func(c *client, err error, t int, retry bool) {
@@ -75,16 +51,6 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
}
}
- authenticate := func(c *client, t int) error {
- var sid string
- if cookie, _ := c.r.Cookie("session_id"); cookie != nil {
- sid = cookie.Value
- }
- csrf := c.r.FormValue("csrf_token")
- ref := c.r.URL.RequestURI()
- return s.authenticate(c, sid, csrf, ref, t)
- }
-
handle := func(f func(c *client) error, at int, rt int) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var err error
@@ -94,26 +60,35 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
r: req,
}
- defer func(begin time.Time) {
- logger.Printf("path=%s, err=%v, took=%v\n",
- req.URL.Path, err, time.Since(begin))
- }(time.Now())
+ if verbose {
+ defer func(begin time.Time) {
+ log.Printf("path=%s, err=%v, took=%v\n",
+ req.URL.Path, err, time.Since(begin))
+ }(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 = authenticate(c, at)
+ err = c.authenticate(at, s.instance)
if err != nil {
writeError(c, err, rt, req.Method == http.MethodGet)
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)
@@ -123,16 +98,16 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
}
rootPage := handle(func(c *client) error {
- err := authenticate(c, SESSION)
+ err := c.authenticate(SESSION, "")
if err != nil {
if err == errInvalidSession {
- redirect(c, "/signin")
+ c.redirect("/signin")
return nil
}
return err
}
if !c.s.IsLoggedIn() {
- redirect(c, "/signin")
+ c.redirect("/signin")
return nil
}
return s.RootPage(c)
@@ -147,12 +122,12 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if !ok {
return s.SigninPage(c)
}
- url, sid, err := s.NewSession(c, instance)
+ url, sess, err := s.NewSession(c, instance)
if err != nil {
return err
}
- setSessionCookie(c.w, sid, sessionExp)
- redirect(c, url)
+ c.setSession(sess)
+ c.redirect(url)
return nil
}, NOAUTH, HTML)
@@ -167,7 +142,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
}, SESSION, HTML)
defaultTimelinePage := handle(func(c *client) error {
- redirect(c, "/timeline/home")
+ c.redirect("/timeline/home")
return nil
}, SESSION, HTML)
@@ -217,6 +192,11 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
return s.UserSearchPage(c, id, sq, offset)
}, SESSION, HTML)
+ mutePage := handle(func(c *client) error {
+ id, _ := mux.Vars(c.r)["id"]
+ return s.MutePage(c, id)
+ }, SESSION, HTML)
+
aboutPage := handle(func(c *client) error {
return s.AboutPage(c)
}, SESSION, HTML)
@@ -241,14 +221,65 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
return s.FiltersPage(c)
}, SESSION, HTML)
+ profilePage := handle(func(c *client) error {
+ return s.ProfilePage(c)
+ }, SESSION, HTML)
+
+ profileUpdate := handle(func(c *client) error {
+ name := c.r.FormValue("name")
+ bio := c.r.FormValue("bio")
+ var avatar, banner *multipart.FileHeader
+ if f := c.r.MultipartForm.File["avatar"]; len(f) > 0 {
+ avatar = f[0]
+ }
+ if f := c.r.MultipartForm.File["banner"]; len(f) > 0 {
+ banner = f[0]
+ }
+ var fields []mastodon.Field
+ for i := 0; i < 16; i++ {
+ n := c.r.FormValue(fmt.Sprintf("field-name-%d", i))
+ v := c.r.FormValue(fmt.Sprintf("field-value-%d", i))
+ if len(n) == 0 {
+ continue
+ }
+ f := mastodon.Field{Name: n, Value: v}
+ fields = append(fields, f)
+ }
+ locked := c.r.FormValue("locked") == "true"
+ err := s.ProfileUpdate(c, name, bio, avatar, banner, fields, locked)
+ if err != nil {
+ return err
+ }
+ c.redirect("/")
+ return nil
+ }, CSRF, HTML)
+
+ profileDelAvatar := handle(func(c *client) error {
+ err := s.ProfileDelAvatar(c)
+ if err != nil {
+ return err
+ }
+ c.redirect(c.r.FormValue("referrer"))
+ return nil
+ }, CSRF, HTML)
+
+ profileDelBanner := handle(func(c *client) error {
+ err := s.ProfileDelBanner(c)
+ if err != nil {
+ return err
+ }
+ c.redirect(c.r.FormValue("referrer"))
+ return nil
+ }, CSRF, HTML)
+
signin := handle(func(c *client) error {
instance := c.r.FormValue("instance")
- url, sid, err := s.NewSession(c, instance)
+ url, sess, err := s.NewSession(c, instance)
if err != nil {
return err
}
- setSessionCookie(c.w, sid, sessionExp)
- redirect(c, url)
+ c.setSession(sess)
+ c.redirect(url)
return nil
}, NOAUTH, HTML)
@@ -259,7 +290,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, "/")
+ c.redirect("/")
return nil
}, SESSION, HTML)
@@ -287,7 +318,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
} else {
location = c.r.FormValue("referrer")
}
- redirect(c, location)
+ c.redirect(location)
return nil
}, CSRF, HTML)
@@ -301,7 +332,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -315,7 +346,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -329,7 +360,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -343,7 +374,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -355,7 +386,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+statusID)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + statusID)
return nil
}, CSRF, HTML)
@@ -371,7 +402,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -381,7 +412,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -391,7 +422,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -401,23 +432,19 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
mute := handle(func(c *client) error {
id, _ := mux.Vars(c.r)["id"]
- q := c.r.URL.Query()
- var notifications *bool
- if r, ok := q["notifications"]; ok && len(r) > 0 {
- notifications = new(bool)
- *notifications = r[0] == "true"
- }
- err := s.Mute(c, id, notifications)
+ notifications, _ := strconv.ParseBool(c.r.FormValue("notifications"))
+ duration, _ := strconv.Atoi(c.r.FormValue("duration"))
+ err := s.Mute(c, id, notifications, duration)
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect("/user/" + id)
return nil
}, CSRF, HTML)
@@ -427,7 +454,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -437,7 +464,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -447,7 +474,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -457,7 +484,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -467,7 +494,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -504,7 +531,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, "/")
+ c.redirect("/")
return nil
}, CSRF, HTML)
@@ -514,7 +541,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -524,7 +551,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -534,7 +561,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -545,7 +572,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -559,7 +586,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -573,7 +600,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if len(rid) > 0 {
id = rid
}
- redirect(c, c.r.FormValue("referrer")+"#status-"+id)
+ c.redirect(c.r.FormValue("referrer") + "#status-" + id)
return nil
}, CSRF, HTML)
@@ -584,7 +611,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -594,7 +621,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -608,7 +635,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -618,7 +645,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -629,7 +656,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -648,7 +675,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
@@ -660,14 +687,17 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- redirect(c, c.r.FormValue("referrer"))
+ c.redirect(c.r.FormValue("referrer"))
return nil
}, CSRF, HTML)
signout := handle(func(c *client) error {
- s.Signout(c)
- setSessionCookie(c.w, "", 0)
- redirect(c, "/")
+ err := s.Signout(c)
+ if err != nil {
+ return err
+ }
+ c.unsetSession()
+ c.redirect("/")
return nil
}, CSRF, HTML)
@@ -677,7 +707,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- return writeJson(c, count)
+ return c.writeJson(count)
}, CSRF, JSON)
fUnlike := handle(func(c *client) error {
@@ -686,7 +716,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- return writeJson(c, count)
+ return c.writeJson(count)
}, CSRF, JSON)
fRetweet := handle(func(c *client) error {
@@ -695,7 +725,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- return writeJson(c, count)
+ return c.writeJson(count)
}, CSRF, JSON)
fUnretweet := handle(func(c *client) error {
@@ -704,7 +734,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
if err != nil {
return err
}
- return writeJson(c, count)
+ return c.writeJson(count)
}, CSRF, JSON)
r.HandleFunc("/", rootPage).Methods(http.MethodGet)
@@ -720,11 +750,16 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
r.HandleFunc("/user/{id}", userPage).Methods(http.MethodGet)
r.HandleFunc("/user/{id}/{type}", userPage).Methods(http.MethodGet)
r.HandleFunc("/usersearch/{id}", userSearchPage).Methods(http.MethodGet)
+ r.HandleFunc("/mute/{id}", mutePage).Methods(http.MethodGet)
r.HandleFunc("/about", aboutPage).Methods(http.MethodGet)
r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet)
r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet)
+ r.HandleFunc("/profile", profilePage).Methods(http.MethodGet)
+ r.HandleFunc("/profile", profileUpdate).Methods(http.MethodPost)
+ 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("/oauth_callback", oauthCallback).Methods(http.MethodGet)
r.HandleFunc("/post", post).Methods(http.MethodPost)