Add CSRF protection
authorr <r@freesoftwareextremist.com>
Sat, 25 Jan 2020 10:07:06 +0000 (10:07 +0000)
committerr <r@freesoftwareextremist.com>
Sun, 26 Jan 2020 06:49:29 +0000 (06:49 +0000)
13 files changed:
migrations/csrfToken/main.go [new file with mode: 0644]
model/session.go
renderer/model.go
service/auth.go
service/service.go
service/transport.go
static/fluoride.js
templates/header.tmpl
templates/postform.tmpl
templates/settings.tmpl
templates/status.tmpl
templates/user.tmpl
util/rand.go

diff --git a/migrations/csrfToken/main.go b/migrations/csrfToken/main.go
new file mode 100644 (file)
index 0000000..fcd49f2
--- /dev/null
@@ -0,0 +1,79 @@
+package main
+
+import (
+       "log"
+       "math/rand"
+       "os"
+       "path/filepath"
+       "time"
+
+       "bloat/config"
+       "bloat/kv"
+       "bloat/repository"
+       "bloat/util"
+)
+
+var (
+       configFile = "bloat.conf"
+)
+
+func init() {
+       rand.Seed(time.Now().Unix())
+}
+
+func getKeys(sessionRepoPath string) (keys []string, err error) {
+       f, err := os.Open(sessionRepoPath)
+       if err != nil {
+               return
+       }
+       return f.Readdirnames(0)
+}
+
+func main() {
+       opts, _, err := util.Getopts(os.Args, "f:")
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       for _, opt := range opts {
+               switch opt.Option {
+               case 'f':
+                       configFile = opt.Value
+               }
+       }
+
+       config, err := config.ParseFile(configFile)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       if !config.IsValid() {
+               log.Fatal("invalid config")
+       }
+
+       sessionRepoPath := filepath.Join(config.DatabasePath, "session")
+       sessionDB, err := kv.NewDatabse(sessionRepoPath)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       sessionRepo := repository.NewSessionRepository(sessionDB)
+
+       sessionIds, err := getKeys(sessionRepoPath)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       for _, id := range sessionIds {
+               s, err := sessionRepo.Get(id)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               s.CSRFToken = util.NewCSRFToken()
+               err = sessionRepo.Add(s)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+
+}
index fce6173159fc0d91f624d450596121f525f8ef57..6bc8a6385f786bdd1c750309d1a958237e4011b6 100644 (file)
@@ -12,6 +12,7 @@ type Session struct {
        ID             string   `json:"id"`
        InstanceDomain string   `json:"instance_domain"`
        AccessToken    string   `json:"access_token"`
+       CSRFToken      string   `json:"csrf_token"`
        Settings       Settings `json:"settings"`
 }
 
index cc0a6ce3f3158b1dabdf6ec704adf05f0fa47514..25fa0c65f7e977fba663ece1402130643cc3af67 100644 (file)
@@ -10,12 +10,14 @@ type Context struct {
        FluorideMode   bool
        ThreadInNewTab bool
        DarkMode       bool
+       CSRFToken      string
 }
 
 type HeaderData struct {
        Title             string
        NotificationCount int
        CustomCSS         string
+       CSRFToken         string
 }
 
 type NavbarData struct {
index e5173836082678b2aafcc6ac5837187489d1a79f..909a9a29bc6a86a236d66fa64a8054c5b4c809d6 100644 (file)
@@ -11,7 +11,8 @@ import (
 )
 
 var (
-       ErrInvalidSession = errors.New("invalid session")
+       ErrInvalidSession   = errors.New("invalid session")
+       ErrInvalidCSRFToken = errors.New("invalid csrf token")
 )
 
 type authService struct {
@@ -47,6 +48,14 @@ func (s *authService) getClient(ctx context.Context) (c *model.Client, err error
        return c, nil
 }
 
+func checkCSRF(ctx context.Context, c *model.Client) (err error) {
+       csrfToken, ok := ctx.Value("csrf_token").(string)
+       if !ok || csrfToken != c.Session.CSRFToken {
+               return ErrInvalidCSRFToken
+       }
+       return nil
+}
+
 func (s *authService) GetAuthUrl(ctx context.Context, instance string) (
        redirectUrl string, sessionID string, err error) {
        return s.Service.GetAuthUrl(ctx, instance)
@@ -184,6 +193,10 @@ func (s *authService) SaveSettings(ctx context.Context, client io.Writer, c *mod
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.SaveSettings(ctx, client, c, settings)
 }
 
@@ -192,6 +205,10 @@ func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Clien
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.Like(ctx, client, c, id)
 }
 
@@ -200,6 +217,10 @@ func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Cli
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.UnLike(ctx, client, c, id)
 }
 
@@ -208,6 +229,10 @@ func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Cl
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.Retweet(ctx, client, c, id)
 }
 
@@ -216,6 +241,10 @@ func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *model.
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.UnRetweet(ctx, client, c, id)
 }
 
@@ -224,6 +253,10 @@ func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *model.
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.PostTweet(ctx, client, c, content, replyToID, format, visibility, isNSFW, files)
 }
 
@@ -232,6 +265,10 @@ func (s *authService) Follow(ctx context.Context, client io.Writer, c *model.Cli
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.Follow(ctx, client, c, id)
 }
 
@@ -240,5 +277,9 @@ func (s *authService) UnFollow(ctx context.Context, client io.Writer, c *model.C
        if err != nil {
                return
        }
+       err = checkCSRF(ctx, c)
+       if err != nil {
+               return
+       }
        return s.Service.UnFollow(ctx, client, c, id)
 }
index bfacf80a64ea8857da1d1d2eef5c3f9925a9e1e6..db851f77d601956ed46fc3acdb6d1cd9017431ff 100644 (file)
@@ -78,12 +78,21 @@ func NewService(clientName string, clientScope string, clientWebsite string,
        }
 }
 
-func getRendererContext(s model.Settings) *renderer.Context {
+func getRendererContext(c *model.Client) *renderer.Context {
+       var settings model.Settings
+       var session model.Session
+       if c != nil {
+               settings = c.Session.Settings
+               session = c.Session
+       } else {
+               settings = *model.NewSettings()
+       }
        return &renderer.Context{
-               MaskNSFW:       s.MaskNSFW,
-               ThreadInNewTab: s.ThreadInNewTab,
-               FluorideMode:   s.FluorideMode,
-               DarkMode:       s.DarkMode,
+               MaskNSFW:       settings.MaskNSFW,
+               ThreadInNewTab: settings.ThreadInNewTab,
+               FluorideMode:   settings.FluorideMode,
+               DarkMode:       settings.DarkMode,
+               CSRFToken:      session.CSRFToken,
        }
 }
 
@@ -98,9 +107,11 @@ func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
        }
 
        sessionID = util.NewSessionId()
+       csrfToken := util.NewCSRFToken()
        session := model.Session{
                ID:             sessionID,
                InstanceDomain: instance,
+               CSRFToken:      csrfToken,
                Settings:       *model.NewSettings(),
        }
        err = svc.sessionRepo.Add(session)
@@ -199,13 +210,6 @@ func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *model
        if err != nil {
                return
        }
-       /*
-               err = c.AuthenticateToken(ctx, code, svc.clientWebsite+"/oauth_callback")
-               if err != nil {
-                       return
-               }
-               err = svc.sessionRepo.Update(sessionID, c.GetAccessToken(ctx))
-       */
 
        return res.AccessToken, nil
 }
@@ -226,13 +230,7 @@ func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, c *mod
                Error:      errStr,
        }
 
-       var s model.Settings
-       if c != nil {
-               s = c.Session.Settings
-       } else {
-               s = *model.NewSettings()
-       }
-       rCtx := getRendererContext(s)
+       rCtx := getRendererContext(c)
 
        svc.renderer.RenderErrorPage(rCtx, client, data)
 }
@@ -247,7 +245,7 @@ func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err
                CommonData: commonData,
        }
 
-       rCtx := getRendererContext(*model.NewSettings())
+       rCtx := getRendererContext(nil)
        return svc.renderer.RenderSigninPage(rCtx, client, data)
 }
 
@@ -334,7 +332,7 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
                PostContext: postContext,
                CommonData:  commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderTimelinePage(rCtx, client, data)
        if err != nil {
@@ -416,7 +414,7 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
                ReplyMap:    replyMap,
                CommonData:  commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderThreadPage(rCtx, client, data)
        if err != nil {
@@ -478,7 +476,7 @@ func (svc *service) ServeNotificationPage(ctx context.Context, client io.Writer,
                NextLink:      nextLink,
                CommonData:    commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderNotificationPage(rCtx, client, data)
        if err != nil {
@@ -525,7 +523,7 @@ func (svc *service) ServeUserPage(ctx context.Context, client io.Writer, c *mode
                NextLink:   nextLink,
                CommonData: commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderUserPage(rCtx, client, data)
        if err != nil {
@@ -544,7 +542,7 @@ func (svc *service) ServeAboutPage(ctx context.Context, client io.Writer, c *mod
        data := &renderer.AboutData{
                CommonData: commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderAboutPage(rCtx, client, data)
        if err != nil {
@@ -569,7 +567,7 @@ func (svc *service) ServeEmojiPage(ctx context.Context, client io.Writer, c *mod
                Emojis:     emojis,
                CommonData: commonData,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderEmojiPage(rCtx, client, data)
        if err != nil {
@@ -594,7 +592,7 @@ func (svc *service) ServeLikedByPage(ctx context.Context, client io.Writer, c *m
                CommonData: commonData,
                Users:      likers,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderLikedByPage(rCtx, client, data)
        if err != nil {
@@ -619,7 +617,7 @@ func (svc *service) ServeRetweetedByPage(ctx context.Context, client io.Writer,
                CommonData: commonData,
                Users:      retweeters,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderRetweetedByPage(rCtx, client, data)
        if err != nil {
@@ -660,7 +658,7 @@ func (svc *service) ServeFollowingPage(ctx context.Context, client io.Writer, c
                HasNext:    hasNext,
                NextLink:   nextLink,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderFollowingPage(rCtx, client, data)
        if err != nil {
@@ -701,7 +699,7 @@ func (svc *service) ServeFollowersPage(ctx context.Context, client io.Writer, c
                HasNext:    hasNext,
                NextLink:   nextLink,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderFollowersPage(rCtx, client, data)
        if err != nil {
@@ -750,7 +748,7 @@ func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *mo
                HasNext:    hasNext,
                NextLink:   nextLink,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderSearchPage(rCtx, client, data)
        if err != nil {
@@ -770,7 +768,7 @@ func (svc *service) ServeSettingsPage(ctx context.Context, client io.Writer, c *
                CommonData: commonData,
                Settings:   &c.Session.Settings,
        }
-       rCtx := getRendererContext(c.Session.Settings)
+       rCtx := getRendererContext(c)
 
        err = svc.renderer.RenderSettingsPage(rCtx, client, data)
        if err != nil {
@@ -828,6 +826,7 @@ func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *mode
                }
 
                data.HeaderData.NotificationCount = notificationCount
+               data.HeaderData.CSRFToken = c.Session.CSRFToken
        }
 
        return
index 8cca4f518991e3abdc78bb6f687d49132e817c49..e878f8d5f06fd0d4405154b9c99339913aa6bc9e 100644 (file)
@@ -160,6 +160,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                retweetedByID := req.FormValue("retweeted_by_id")
 
@@ -179,6 +181,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                retweetedByID := req.FormValue("retweeted_by_id")
 
@@ -198,6 +202,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                retweetedByID := req.FormValue("retweeted_by_id")
 
@@ -217,6 +223,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                retweetedByID := req.FormValue("retweeted_by_id")
 
@@ -236,6 +244,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/fluoride/like/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                count, err := s.Like(ctx, w, nil, id)
                if err != nil {
@@ -252,6 +262,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/fluoride/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                count, err := s.UnLike(ctx, w, nil, id)
                if err != nil {
@@ -268,6 +280,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/fluoride/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                count, err := s.Retweet(ctx, w, nil, id)
                if err != nil {
@@ -284,6 +298,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/fluoride/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
+
                id, _ := mux.Vars(req)["id"]
                count, err := s.UnRetweet(ctx, w, nil, id)
                if err != nil {
@@ -299,14 +315,16 @@ func NewHandler(s Service, staticDir string) http.Handler {
        }).Methods(http.MethodPost)
 
        r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
-               ctx := getContextWithSession(context.Background(), req)
-
                err := req.ParseMultipartForm(4 << 20)
                if err != nil {
                        s.ServeErrorPage(ctx, w, nil, err)
                        return
                }
 
+               ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token",
+                       getMultipartFormValue(req.MultipartForm, "csrf_token"))
+
                content := getMultipartFormValue(req.MultipartForm, "content")
                replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
                format := getMultipartFormValue(req.MultipartForm, "format")
@@ -358,6 +376,7 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
 
                id, _ := mux.Vars(req)["id"]
 
@@ -373,6 +392,7 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
 
                id, _ := mux.Vars(req)["id"]
 
@@ -442,6 +462,7 @@ func NewHandler(s Service, staticDir string) http.Handler {
 
        r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
                ctx := getContextWithSession(context.Background(), req)
+               ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
 
                visibility := req.FormValue("visibility")
                copyScope := req.FormValue("copy_scope") == "true"
index 6a1b5fbca7adfad5110a236add8297436cec05c8..3c0d7f2b2d17ff7f462c87b935428ed8ff2e34e0 100644 (file)
@@ -16,7 +16,14 @@ var reverseActions = {
        "unretweet": "retweet"
 };
 
-function http(method, url, success, error) {
+function getCSRFToken() {
+       var tag = document.querySelector("meta[name='csrf_token']")
+       if (tag)
+               return tag.getAttribute("content");
+       return "";
+}
+
+function http(method, url, body, type, success, error) {
        var req = new XMLHttpRequest();
        req.onload = function() {
                if (this.status === 200 && typeof success === "function") {
@@ -31,14 +38,15 @@ function http(method, url, success, error) {
                }
        };
        req.open(method, url);
-       req.send();
+       req.setRequestHeader("Content-Type", type);
+       req.send(body);
 }
 
 function updateActionForm(id, f, action) {
        if (Array.from(document.body.classList).indexOf("dark") > -1) {
-               f.children[1].src = actionIcons["dark-" + action];
+               f.querySelector(".icon").src = actionIcons["dark-" + action];
        } else {
-               f.children[1].src = actionIcons[action];
+               f.querySelector(".icon").src = actionIcons[action];
        }
        f.action = "/" + action + "/" + id;
        f.dataset.action = action;
@@ -54,7 +62,9 @@ function handleLikeForm(id, f) {
                        updateActionForm(id, f, reverseActions[action]);
                });
 
-               http("POST", "/fluoride/" + action + "/" + id, function(res, type) {
+               var body = "csrf_token=" + encodeURIComponent(getCSRFToken());
+               var contentType = "application/x-www-form-urlencoded";
+               http("POST", "/fluoride/" + action + "/" + id, body, contentType, function(res, type) {
                        var data = JSON.parse(res);
                        var count = data.data;
                        if (count === 0) {
@@ -82,7 +92,9 @@ function handleRetweetForm(id, f) {
                        updateActionForm(id, f, reverseActions[action]);
                });
 
-               http("POST", "/fluoride/" + action + "/" + id, function(res, type) {
+               var body = "csrf_token=" + encodeURIComponent(getCSRFToken());
+               var contentType = "application/x-www-form-urlencoded";
+               http("POST", "/fluoride/" + action + "/" + id, body, contentType, function(res, type) {
                        var data = JSON.parse(res);
                        var count = data.data;
                        if (count === 0) {
index 571008aefae6507fa9d481a825b9ded23bac5b68..e6e7f0d70655b4d537283fb09bab1cfdb0e002e2 100644 (file)
@@ -4,6 +4,9 @@
 <head>
        <meta charset='utf-8'>
        <meta content='width=device-width, initial-scale=1' name='viewport'>
+       {{if .CSRFToken}}
+       <meta name="csrf_token" content="{{.CSRFToken}}">
+       {{end}}
        <title>{{if gt .NotificationCount 0}}({{.NotificationCount}}) {{end}}{{.Title}}</title>
        <link rel="stylesheet" href="/static/main.css">
        {{if .CustomCSS}}
index 0b83d2cd7766a82f6e2e5a32ed0530aa5908f43c..ff2dfd9130e94699c80b3cd2554d4b770bad19e3 100644 (file)
@@ -1,5 +1,6 @@
 {{with .Data}}
 <form class="post-form" action="/post" method="POST" enctype="multipart/form-data">
+       <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
        {{if .ReplyContext}}
        <input type="hidden" name="reply_to_id" value="{{.ReplyContext.InReplyToID}}" />
        <label for="post-content" class="post-form-title"> Reply to {{.ReplyContext.InReplyToName}} </label>
index a32a1b041a9894f573c7b90521c652d4ba6639fa..e7d49e9b44ebf335b6fda00b2dfbfce56883e68e 100644 (file)
@@ -4,6 +4,7 @@
 <div class="page-title"> Settings </div>
 
 <form id="settings-form" action="/settings" method="POST">
+       <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
        <div class="settings-form-field">
                <label for="visibility"> Default scope </label>
                <select id="visibility" name="visibility">
index 09c13547e6b96ff4940bec46c1bfe7987218051a..fd5339a3f41f0a2e6a5afc3e6a5eac2838c8754a 100644 (file)
                                        {{else}}
                                        {{if .Reblogged}}
                                        <form class="status-retweet" data-action="unretweet" action="/unretweet/{{.ID}}" method="post">
-                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
+                                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
                                                <input type="image" src="{{GetIcon "retweeted" $.Ctx.DarkMode}}" alt="undo retweet" class="icon" title="undo retweet">
                                        </form>
                                        {{else}}
                                        <form class="status-retweet" data-action="retweet" action="/retweet/{{.ID}}" method="post">
-                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
+                                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
                                                <input type="image" src="{{GetIcon "retweet" $.Ctx.DarkMode}}" alt="retweet" class="icon" title="retweet">
                                        </form>
                                        {{end}}
                                <div class="status-action">
                                        {{if .Favourited}}
                                        <form class="status-like" data-action="unlike" action="/unlike/{{.ID}}" method="post">
-                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
+                                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
                                                <input type="image" src="{{GetIcon "liked" $.Ctx.DarkMode}}" alt="unlike" class="icon" title="unlike">
                                        </form>
                                        {{else}}
                                        <form class="status-like" data-action="like" action="/like/{{.ID}}" method="post">
-                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
+                                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                                               <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}">
                                                <input type="image" src="{{GetIcon "star-o" $.Ctx.DarkMode}}" alt="like" class="icon" title="like">
                                        </form>
                                        {{end}}
index bbbce329e4844ccec383ace1dae7f6a8ea698b1a..abf22ec4f2d44cc09b07e75a8a7fd60655b12c9a 100644 (file)
                        <span> {{if .User.Pleroma.Relationship.FollowedBy}} follows you - {{end}} </span>  
                        {{if .User.Pleroma.Relationship.Following}} 
                        <form class="d-inline" action="/unfollow/{{.User.ID}}" method="post">
-                           <input type="submit" value="unfollow" class="btn-link">
+                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                               <input type="submit" value="unfollow" class="btn-link">
                        </form>
                        {{end}} 
                        {{if .User.Pleroma.Relationship.Requested}} 
                        <form class="d-inline" action="/unfollow/{{.User.ID}}" method="post">
-                           <input type="submit" value="cancel request" class="btn-link">
+                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                               <input type="submit" value="cancel request" class="btn-link">
                        </form>
                        {{end}} 
                        {{if not .User.Pleroma.Relationship.Following}} 
                        <form class="d-inline" action="/follow/{{.User.ID}}" method="post">
-                           <input type="submit" value="{{if .User.Pleroma.Relationship.Requested}}resend request{{else}}follow{{end}}" class="btn-link">
+                               <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+                               <input type="submit" value="{{if .User.Pleroma.Relationship.Requested}}resend request{{else}}follow{{end}}" class="btn-link">
                        </form>
                        {{end}} 
                </div>
index 850252194f548cd38d251938352b5df4601b88fa..212d6d33f63b477089bf8443da9cd53e65995179 100644 (file)
@@ -20,3 +20,7 @@ func NewRandId(n int) string {
 func NewSessionId() string {
        return NewRandId(24)
 }
+
+func NewCSRFToken() string {
+       return NewRandId(24)
+}