Add CSRF protection
This commit is contained in:
parent
5fdc7a59b2
commit
bf2cfaf0ed
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ type Session struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
InstanceDomain string `json:"instance_domain"`
|
InstanceDomain string `json:"instance_domain"`
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
|
CSRFToken string `json:"csrf_token"`
|
||||||
Settings Settings `json:"settings"`
|
Settings Settings `json:"settings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,14 @@ type Context struct {
|
||||||
FluorideMode bool
|
FluorideMode bool
|
||||||
ThreadInNewTab bool
|
ThreadInNewTab bool
|
||||||
DarkMode bool
|
DarkMode bool
|
||||||
|
CSRFToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderData struct {
|
type HeaderData struct {
|
||||||
Title string
|
Title string
|
||||||
NotificationCount int
|
NotificationCount int
|
||||||
CustomCSS string
|
CustomCSS string
|
||||||
|
CSRFToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavbarData struct {
|
type NavbarData struct {
|
||||||
|
|
|
@ -11,7 +11,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidSession = errors.New("invalid session")
|
ErrInvalidSession = errors.New("invalid session")
|
||||||
|
ErrInvalidCSRFToken = errors.New("invalid csrf token")
|
||||||
)
|
)
|
||||||
|
|
||||||
type authService struct {
|
type authService struct {
|
||||||
|
@ -47,6 +48,14 @@ func (s *authService) getClient(ctx context.Context) (c *model.Client, err error
|
||||||
return c, nil
|
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) (
|
func (s *authService) GetAuthUrl(ctx context.Context, instance string) (
|
||||||
redirectUrl string, sessionID string, err error) {
|
redirectUrl string, sessionID string, err error) {
|
||||||
return s.Service.GetAuthUrl(ctx, instance)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.SaveSettings(ctx, client, c, settings)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.Like(ctx, client, c, id)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.UnLike(ctx, client, c, id)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.Retweet(ctx, client, c, id)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.UnRetweet(ctx, client, c, id)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.PostTweet(ctx, client, c, content, replyToID, format, visibility, isNSFW, files)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.Follow(ctx, client, c, id)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = checkCSRF(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return s.Service.UnFollow(ctx, client, c, id)
|
return s.Service.UnFollow(ctx, client, c, id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
return &renderer.Context{
|
||||||
MaskNSFW: s.MaskNSFW,
|
MaskNSFW: settings.MaskNSFW,
|
||||||
ThreadInNewTab: s.ThreadInNewTab,
|
ThreadInNewTab: settings.ThreadInNewTab,
|
||||||
FluorideMode: s.FluorideMode,
|
FluorideMode: settings.FluorideMode,
|
||||||
DarkMode: s.DarkMode,
|
DarkMode: settings.DarkMode,
|
||||||
|
CSRFToken: session.CSRFToken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +107,11 @@ func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionID = util.NewSessionId()
|
sessionID = util.NewSessionId()
|
||||||
|
csrfToken := util.NewCSRFToken()
|
||||||
session := model.Session{
|
session := model.Session{
|
||||||
ID: sessionID,
|
ID: sessionID,
|
||||||
InstanceDomain: instance,
|
InstanceDomain: instance,
|
||||||
|
CSRFToken: csrfToken,
|
||||||
Settings: *model.NewSettings(),
|
Settings: *model.NewSettings(),
|
||||||
}
|
}
|
||||||
err = svc.sessionRepo.Add(session)
|
err = svc.sessionRepo.Add(session)
|
||||||
|
@ -199,13 +210,6 @@ func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *model
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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
|
return res.AccessToken, nil
|
||||||
}
|
}
|
||||||
|
@ -226,13 +230,7 @@ func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, c *mod
|
||||||
Error: errStr,
|
Error: errStr,
|
||||||
}
|
}
|
||||||
|
|
||||||
var s model.Settings
|
rCtx := getRendererContext(c)
|
||||||
if c != nil {
|
|
||||||
s = c.Session.Settings
|
|
||||||
} else {
|
|
||||||
s = *model.NewSettings()
|
|
||||||
}
|
|
||||||
rCtx := getRendererContext(s)
|
|
||||||
|
|
||||||
svc.renderer.RenderErrorPage(rCtx, client, data)
|
svc.renderer.RenderErrorPage(rCtx, client, data)
|
||||||
}
|
}
|
||||||
|
@ -247,7 +245,7 @@ func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
|
|
||||||
rCtx := getRendererContext(*model.NewSettings())
|
rCtx := getRendererContext(nil)
|
||||||
return svc.renderer.RenderSigninPage(rCtx, client, data)
|
return svc.renderer.RenderSigninPage(rCtx, client, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +332,7 @@ func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
|
||||||
PostContext: postContext,
|
PostContext: postContext,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderTimelinePage(rCtx, client, data)
|
err = svc.renderer.RenderTimelinePage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -416,7 +414,7 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
|
||||||
ReplyMap: replyMap,
|
ReplyMap: replyMap,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderThreadPage(rCtx, client, data)
|
err = svc.renderer.RenderThreadPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -478,7 +476,7 @@ func (svc *service) ServeNotificationPage(ctx context.Context, client io.Writer,
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderNotificationPage(rCtx, client, data)
|
err = svc.renderer.RenderNotificationPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -525,7 +523,7 @@ func (svc *service) ServeUserPage(ctx context.Context, client io.Writer, c *mode
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderUserPage(rCtx, client, data)
|
err = svc.renderer.RenderUserPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -544,7 +542,7 @@ func (svc *service) ServeAboutPage(ctx context.Context, client io.Writer, c *mod
|
||||||
data := &renderer.AboutData{
|
data := &renderer.AboutData{
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderAboutPage(rCtx, client, data)
|
err = svc.renderer.RenderAboutPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -569,7 +567,7 @@ func (svc *service) ServeEmojiPage(ctx context.Context, client io.Writer, c *mod
|
||||||
Emojis: emojis,
|
Emojis: emojis,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderEmojiPage(rCtx, client, data)
|
err = svc.renderer.RenderEmojiPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -594,7 +592,7 @@ func (svc *service) ServeLikedByPage(ctx context.Context, client io.Writer, c *m
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
Users: likers,
|
Users: likers,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderLikedByPage(rCtx, client, data)
|
err = svc.renderer.RenderLikedByPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -619,7 +617,7 @@ func (svc *service) ServeRetweetedByPage(ctx context.Context, client io.Writer,
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
Users: retweeters,
|
Users: retweeters,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderRetweetedByPage(rCtx, client, data)
|
err = svc.renderer.RenderRetweetedByPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -660,7 +658,7 @@ func (svc *service) ServeFollowingPage(ctx context.Context, client io.Writer, c
|
||||||
HasNext: hasNext,
|
HasNext: hasNext,
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderFollowingPage(rCtx, client, data)
|
err = svc.renderer.RenderFollowingPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -701,7 +699,7 @@ func (svc *service) ServeFollowersPage(ctx context.Context, client io.Writer, c
|
||||||
HasNext: hasNext,
|
HasNext: hasNext,
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderFollowersPage(rCtx, client, data)
|
err = svc.renderer.RenderFollowersPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -750,7 +748,7 @@ func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *mo
|
||||||
HasNext: hasNext,
|
HasNext: hasNext,
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderSearchPage(rCtx, client, data)
|
err = svc.renderer.RenderSearchPage(rCtx, client, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -770,7 +768,7 @@ func (svc *service) ServeSettingsPage(ctx context.Context, client io.Writer, c *
|
||||||
CommonData: commonData,
|
CommonData: commonData,
|
||||||
Settings: &c.Session.Settings,
|
Settings: &c.Session.Settings,
|
||||||
}
|
}
|
||||||
rCtx := getRendererContext(c.Session.Settings)
|
rCtx := getRendererContext(c)
|
||||||
|
|
||||||
err = svc.renderer.RenderSettingsPage(rCtx, client, data)
|
err = svc.renderer.RenderSettingsPage(rCtx, client, data)
|
||||||
if err != nil {
|
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.NotificationCount = notificationCount
|
||||||
|
data.HeaderData.CSRFToken = c.Session.CSRFToken
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -160,6 +160,8 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
|
|
||||||
r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
|
r.HandleFunc("/like/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
retweetedByID := req.FormValue("retweeted_by_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) {
|
r.HandleFunc("/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
retweetedByID := req.FormValue("retweeted_by_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) {
|
r.HandleFunc("/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
retweetedByID := req.FormValue("retweeted_by_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) {
|
r.HandleFunc("/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
retweetedByID := req.FormValue("retweeted_by_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) {
|
r.HandleFunc("/fluoride/like/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
count, err := s.Like(ctx, w, nil, id)
|
count, err := s.Like(ctx, w, nil, id)
|
||||||
if err != nil {
|
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) {
|
r.HandleFunc("/fluoride/unlike/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
count, err := s.UnLike(ctx, w, nil, id)
|
count, err := s.UnLike(ctx, w, nil, id)
|
||||||
if err != nil {
|
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) {
|
r.HandleFunc("/fluoride/retweet/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
count, err := s.Retweet(ctx, w, nil, id)
|
count, err := s.Retweet(ctx, w, nil, id)
|
||||||
if err != nil {
|
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) {
|
r.HandleFunc("/fluoride/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
id, _ := mux.Vars(req)["id"]
|
||||||
count, err := s.UnRetweet(ctx, w, nil, id)
|
count, err := s.UnRetweet(ctx, w, nil, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -299,14 +315,16 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
}).Methods(http.MethodPost)
|
}).Methods(http.MethodPost)
|
||||||
|
|
||||||
r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
|
r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
|
||||||
|
|
||||||
err := req.ParseMultipartForm(4 << 20)
|
err := req.ParseMultipartForm(4 << 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ServeErrorPage(ctx, w, nil, err)
|
s.ServeErrorPage(ctx, w, nil, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token",
|
||||||
|
getMultipartFormValue(req.MultipartForm, "csrf_token"))
|
||||||
|
|
||||||
content := getMultipartFormValue(req.MultipartForm, "content")
|
content := getMultipartFormValue(req.MultipartForm, "content")
|
||||||
replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
|
replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
|
||||||
format := getMultipartFormValue(req.MultipartForm, "format")
|
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) {
|
r.HandleFunc("/follow/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
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) {
|
r.HandleFunc("/unfollow/{id}", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
id, _ := mux.Vars(req)["id"]
|
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) {
|
r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
|
ctx = context.WithValue(ctx, "csrf_token", req.FormValue("csrf_token"))
|
||||||
|
|
||||||
visibility := req.FormValue("visibility")
|
visibility := req.FormValue("visibility")
|
||||||
copyScope := req.FormValue("copy_scope") == "true"
|
copyScope := req.FormValue("copy_scope") == "true"
|
||||||
|
|
|
@ -16,7 +16,14 @@ var reverseActions = {
|
||||||
"unretweet": "retweet"
|
"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();
|
var req = new XMLHttpRequest();
|
||||||
req.onload = function() {
|
req.onload = function() {
|
||||||
if (this.status === 200 && typeof success === "function") {
|
if (this.status === 200 && typeof success === "function") {
|
||||||
|
@ -31,14 +38,15 @@ function http(method, url, success, error) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
req.open(method, url);
|
req.open(method, url);
|
||||||
req.send();
|
req.setRequestHeader("Content-Type", type);
|
||||||
|
req.send(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateActionForm(id, f, action) {
|
function updateActionForm(id, f, action) {
|
||||||
if (Array.from(document.body.classList).indexOf("dark") > -1) {
|
if (Array.from(document.body.classList).indexOf("dark") > -1) {
|
||||||
f.children[1].src = actionIcons["dark-" + action];
|
f.querySelector(".icon").src = actionIcons["dark-" + action];
|
||||||
} else {
|
} else {
|
||||||
f.children[1].src = actionIcons[action];
|
f.querySelector(".icon").src = actionIcons[action];
|
||||||
}
|
}
|
||||||
f.action = "/" + action + "/" + id;
|
f.action = "/" + action + "/" + id;
|
||||||
f.dataset.action = action;
|
f.dataset.action = action;
|
||||||
|
@ -54,7 +62,9 @@ function handleLikeForm(id, f) {
|
||||||
updateActionForm(id, f, reverseActions[action]);
|
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 data = JSON.parse(res);
|
||||||
var count = data.data;
|
var count = data.data;
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
|
@ -82,7 +92,9 @@ function handleRetweetForm(id, f) {
|
||||||
updateActionForm(id, f, reverseActions[action]);
|
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 data = JSON.parse(res);
|
||||||
var count = data.data;
|
var count = data.data;
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<meta content='width=device-width, initial-scale=1' name='viewport'>
|
<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>
|
<title>{{if gt .NotificationCount 0}}({{.NotificationCount}}) {{end}}{{.Title}}</title>
|
||||||
<link rel="stylesheet" href="/static/main.css">
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
{{if .CustomCSS}}
|
{{if .CustomCSS}}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{with .Data}}
|
{{with .Data}}
|
||||||
<form class="post-form" action="/post" method="POST" enctype="multipart/form-data">
|
<form class="post-form" action="/post" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
{{if .ReplyContext}}
|
{{if .ReplyContext}}
|
||||||
<input type="hidden" name="reply_to_id" value="{{.ReplyContext.InReplyToID}}" />
|
<input type="hidden" name="reply_to_id" value="{{.ReplyContext.InReplyToID}}" />
|
||||||
<label for="post-content" class="post-form-title"> Reply to {{.ReplyContext.InReplyToName}} </label>
|
<label for="post-content" class="post-form-title"> Reply to {{.ReplyContext.InReplyToName}} </label>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<div class="page-title"> Settings </div>
|
<div class="page-title"> Settings </div>
|
||||||
|
|
||||||
<form id="settings-form" action="/settings" method="POST">
|
<form id="settings-form" action="/settings" method="POST">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
<div class="settings-form-field">
|
<div class="settings-form-field">
|
||||||
<label for="visibility"> Default scope </label>
|
<label for="visibility"> Default scope </label>
|
||||||
<select id="visibility" name="visibility">
|
<select id="visibility" name="visibility">
|
||||||
|
|
|
@ -109,12 +109,14 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{if .Reblogged}}
|
{{if .Reblogged}}
|
||||||
<form class="status-retweet" data-action="unretweet" action="/unretweet/{{.ID}}" method="post">
|
<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">
|
<input type="image" src="{{GetIcon "retweeted" $.Ctx.DarkMode}}" alt="undo retweet" class="icon" title="undo retweet">
|
||||||
</form>
|
</form>
|
||||||
{{else}}
|
{{else}}
|
||||||
<form class="status-retweet" data-action="retweet" action="/retweet/{{.ID}}" method="post">
|
<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">
|
<input type="image" src="{{GetIcon "retweet" $.Ctx.DarkMode}}" alt="retweet" class="icon" title="retweet">
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -126,12 +128,14 @@
|
||||||
<div class="status-action">
|
<div class="status-action">
|
||||||
{{if .Favourited}}
|
{{if .Favourited}}
|
||||||
<form class="status-like" data-action="unlike" action="/unlike/{{.ID}}" method="post">
|
<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">
|
<input type="image" src="{{GetIcon "liked" $.Ctx.DarkMode}}" alt="unlike" class="icon" title="unlike">
|
||||||
</form>
|
</form>
|
||||||
{{else}}
|
{{else}}
|
||||||
<form class="status-like" data-action="like" action="/like/{{.ID}}" method="post">
|
<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">
|
<input type="image" src="{{GetIcon "star-o" $.Ctx.DarkMode}}" alt="like" class="icon" title="like">
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -22,17 +22,20 @@
|
||||||
<span> {{if .User.Pleroma.Relationship.FollowedBy}} follows you - {{end}} </span>
|
<span> {{if .User.Pleroma.Relationship.FollowedBy}} follows you - {{end}} </span>
|
||||||
{{if .User.Pleroma.Relationship.Following}}
|
{{if .User.Pleroma.Relationship.Following}}
|
||||||
<form class="d-inline" action="/unfollow/{{.User.ID}}" method="post">
|
<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>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .User.Pleroma.Relationship.Requested}}
|
{{if .User.Pleroma.Relationship.Requested}}
|
||||||
<form class="d-inline" action="/unfollow/{{.User.ID}}" method="post">
|
<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>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if not .User.Pleroma.Relationship.Following}}
|
{{if not .User.Pleroma.Relationship.Following}}
|
||||||
<form class="d-inline" action="/follow/{{.User.ID}}" method="post">
|
<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>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,3 +20,7 @@ func NewRandId(n int) string {
|
||||||
func NewSessionId() string {
|
func NewSessionId() string {
|
||||||
return NewRandId(24)
|
return NewRandId(24)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCSRFToken() string {
|
||||||
|
return NewRandId(24)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue