aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorr <r@freesoftwareextremist.com>2020-01-25 10:07:06 +0000
committerr <r@freesoftwareextremist.com>2020-01-26 06:49:29 +0000
commitbf2cfaf0ede0e9744408f52538fb4bcd87a6d5b8 (patch)
tree5d3be1dfa65395bddedd2fb6f06a990c23274f00
parent5fdc7a59b2efc60e35f5421e28986c356810456e (diff)
downloadbloat-bf2cfaf0ede0e9744408f52538fb4bcd87a6d5b8.tar.gz
bloat-bf2cfaf0ede0e9744408f52538fb4bcd87a6d5b8.zip
Add CSRF protection
-rw-r--r--migrations/csrfToken/main.go79
-rw-r--r--model/session.go1
-rw-r--r--renderer/model.go2
-rw-r--r--service/auth.go43
-rw-r--r--service/service.go63
-rw-r--r--service/transport.go25
-rw-r--r--static/fluoride.js24
-rw-r--r--templates/header.tmpl3
-rw-r--r--templates/postform.tmpl1
-rw-r--r--templates/settings.tmpl1
-rw-r--r--templates/status.tmpl12
-rw-r--r--templates/user.tmpl9
-rw-r--r--util/rand.go4
13 files changed, 219 insertions, 48 deletions
diff --git a/migrations/csrfToken/main.go b/migrations/csrfToken/main.go
new file mode 100644
index 0000000..fcd49f2
--- /dev/null
+++ b/migrations/csrfToken/main.go
@@ -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)
+ }
+ }
+
+}
diff --git a/model/session.go b/model/session.go
index fce6173..6bc8a63 100644
--- a/model/session.go
+++ b/model/session.go
@@ -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"`
}
diff --git a/renderer/model.go b/renderer/model.go
index cc0a6ce..25fa0c6 100644
--- a/renderer/model.go
+++ b/renderer/model.go
@@ -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 {
diff --git a/service/auth.go b/service/auth.go
index e517383..909a9a2 100644
--- a/service/auth.go
+++ b/service/auth.go
@@ -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)
}
diff --git a/service/service.go b/service/service.go
index bfacf80..db851f7 100644
--- a/service/service.go
+++ b/service/service.go
@@ -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
diff --git a/service/transport.go b/service/transport.go
index 8cca4f5..e878f8d 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -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"
diff --git a/static/fluoride.js b/static/fluoride.js
index 6a1b5fb..3c0d7f2 100644
--- a/static/fluoride.js
+++ b/static/fluoride.js
@@ -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) {
diff --git a/templates/header.tmpl b/templates/header.tmpl
index 571008a..e6e7f0d 100644
--- a/templates/header.tmpl
+++ b/templates/header.tmpl
@@ -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}}
diff --git a/templates/postform.tmpl b/templates/postform.tmpl
index 0b83d2c..ff2dfd9 100644
--- a/templates/postform.tmpl
+++ b/templates/postform.tmpl
@@ -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>
diff --git a/templates/settings.tmpl b/templates/settings.tmpl
index a32a1b0..e7d49e9 100644
--- a/templates/settings.tmpl
+++ b/templates/settings.tmpl
@@ -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">
diff --git a/templates/status.tmpl b/templates/status.tmpl
index 09c1354..fd5339a 100644
--- a/templates/status.tmpl
+++ b/templates/status.tmpl
@@ -109,12 +109,14 @@
{{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}}
@@ -126,12 +128,14 @@
<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}}
diff --git a/templates/user.tmpl b/templates/user.tmpl
index bbbce32..abf22ec 100644
--- a/templates/user.tmpl
+++ b/templates/user.tmpl
@@ -22,17 +22,20 @@
<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>
diff --git a/util/rand.go b/util/rand.go
index 8502521..212d6d3 100644
--- a/util/rand.go
+++ b/util/rand.go
@@ -20,3 +20,7 @@ func NewRandId(n int) string {
func NewSessionId() string {
return NewRandId(24)
}
+
+func NewCSRFToken() string {
+ return NewRandId(24)
+}