From 0e8174b020f9d9bbe00aa9d3cc7c286844e8088c Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Tue, 2 Feb 2021 16:54:15 -0800 Subject: [PATCH 1/7] Add X-Requested-With header check for CSRF --- server/api/api.go | 65 +++++++++++++++++++++++++++++----------- webapp/src/octoClient.ts | 1 + 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 99a4d80ca8f..f5f0465755c 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -18,6 +18,11 @@ import ( "github.com/mattermost/focalboard/server/utils" ) +const ( + HEADER_REQUESTED_WITH = "X-Requested-With" + HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" +) + // ---------------------------------------------------------------------------------------------------- // REST APIs @@ -35,35 +40,61 @@ func (a *API) app() *app.App { } func (a *API) RegisterRoutes(r *mux.Router) { - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET") - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") - r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") - r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") + a.addHandler(r, "/api/v1/blocks", "GET", a.sessionRequired(a.handleGetBlocks)) + a.addHandler(r, "/api/v1/blocks", "POST", a.sessionRequired(a.handlePostBlocks)) + a.addHandler(r, "/api/v1/blocks/{blockID}", "DELETE", a.sessionRequired(a.handleDeleteBlock)) + a.addHandler(r, "/api/v1/blocks/{blockID}/subtree", "GET", a.attachSession(a.handleGetSubTree, false)) - r.HandleFunc("/api/v1/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") + a.addHandler(r, "/api/v1/users/me", "GET", a.sessionRequired(a.handleGetMe)) + a.addHandler(r, "/api/v1/users/{userID}", "GET", a.sessionRequired(a.handleGetUser)) + a.addHandler(r, "/api/v1/users/{userID}/changepassword", "POST", a.sessionRequired(a.handleChangePassword)) - r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST") - r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST") + a.addHandler(r, "/api/v1/login", "POST", a.sessionRequired(a.handleLogin)) + a.addHandler(r, "/api/v1/register", "POST", a.sessionRequired(a.handleRegister)) - r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") - r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") + a.addHandler(r, "api/v1/files", "POST", a.sessionRequired(a.handleUploadFile)) + a.addHandler(r, "/files/{filename}", "GET", a.sessionRequired(a.handleServeFile)) - r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") - r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") + a.addHandler(r, "/api/v1/blocks/export", "GET", a.sessionRequired(a.handleExport)) + a.addHandler(r, "/api/v1/blocks/import", "POST", a.sessionRequired(a.handleImport)) - r.HandleFunc("/api/v1/sharing/{rootID}", a.sessionRequired(a.handlePostSharing)).Methods("POST") - r.HandleFunc("/api/v1/sharing/{rootID}", a.handleGetSharing).Methods("GET") + a.addHandler(r, "/api/v1/sharing/{rootID}", "POST", a.sessionRequired(a.handlePostSharing)) + a.addHandler(r, "/api/v1/sharing/{rootID}", "GET", a.sessionRequired(a.handleGetSharing)) - r.HandleFunc("/api/v1/workspace", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") - r.HandleFunc("/api/v1/workspace/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") + a.addHandler(r, "/api/v1/workspace", "GET", a.sessionRequired(a.handleGetWorkspace)) + a.addHandler(r, "/api/v1/workspace/regenerate_signup_token", "POST", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)) } func (a *API) RegisterAdminRoutes(r *mux.Router) { r.HandleFunc("/api/v1/admin/users/{username}/password", a.adminRequired(a.handleAdminSetPassword)).Methods("POST") } +func (a *API) addHandler(r *mux.Router, path string, method string, f func(http.ResponseWriter, *http.Request)) { + r.HandleFunc(path, a.preHandle(f)).Methods(method) +} + +func (a *API) preHandle(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if !a.checkCSRFToken(r) { + log.Println("checkCSRFToken FAILED") + errorResponse(w, http.StatusBadRequest, nil, nil) + return + } + + handler(w, r) + } +} + +func (a *API) checkCSRFToken(r *http.Request) bool { + token := r.Header.Get(HEADER_REQUESTED_WITH) + + if token == HEADER_REQUESTED_WITH_XML { + return true + } + + return false +} + func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() parentID := query.Get("parent_id") diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index b2ff157f28a..b6b45346a8e 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -87,6 +87,7 @@ class OctoClient { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: this.token ? 'Bearer ' + this.token : '', + 'X-Requested-With': 'XMLHttpRequest', } } From d257b1fef58a7a16ae1ed089043cb801213c8a1a Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Tue, 2 Feb 2021 16:54:15 -0800 Subject: [PATCH 2/7] Add X-Requested-With header check for CSRF --- server/api/api.go | 65 +++++++++++++++++++++++++++++----------- webapp/src/octoClient.ts | 1 + 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 7e7dbcab082..320af0ef1b0 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -18,6 +18,11 @@ import ( "github.com/mattermost/focalboard/server/utils" ) +const ( + HEADER_REQUESTED_WITH = "X-Requested-With" + HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" +) + // ---------------------------------------------------------------------------------------------------- // REST APIs @@ -35,35 +40,61 @@ func (a *API) app() *app.App { } func (a *API) RegisterRoutes(r *mux.Router) { - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET") - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") - r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") - r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") + a.addHandler(r, "/api/v1/blocks", "GET", a.sessionRequired(a.handleGetBlocks)) + a.addHandler(r, "/api/v1/blocks", "POST", a.sessionRequired(a.handlePostBlocks)) + a.addHandler(r, "/api/v1/blocks/{blockID}", "DELETE", a.sessionRequired(a.handleDeleteBlock)) + a.addHandler(r, "/api/v1/blocks/{blockID}/subtree", "GET", a.attachSession(a.handleGetSubTree, false)) - r.HandleFunc("/api/v1/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") + a.addHandler(r, "/api/v1/users/me", "GET", a.sessionRequired(a.handleGetMe)) + a.addHandler(r, "/api/v1/users/{userID}", "GET", a.sessionRequired(a.handleGetUser)) + a.addHandler(r, "/api/v1/users/{userID}/changepassword", "POST", a.sessionRequired(a.handleChangePassword)) - r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST") - r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST") + a.addHandler(r, "/api/v1/login", "POST", a.sessionRequired(a.handleLogin)) + a.addHandler(r, "/api/v1/register", "POST", a.sessionRequired(a.handleRegister)) - r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") - r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") + a.addHandler(r, "api/v1/files", "POST", a.sessionRequired(a.handleUploadFile)) + a.addHandler(r, "/files/{filename}", "GET", a.sessionRequired(a.handleServeFile)) - r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") - r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") + a.addHandler(r, "/api/v1/blocks/export", "GET", a.sessionRequired(a.handleExport)) + a.addHandler(r, "/api/v1/blocks/import", "POST", a.sessionRequired(a.handleImport)) - r.HandleFunc("/api/v1/sharing/{rootID}", a.sessionRequired(a.handlePostSharing)).Methods("POST") - r.HandleFunc("/api/v1/sharing/{rootID}", a.handleGetSharing).Methods("GET") + a.addHandler(r, "/api/v1/sharing/{rootID}", "POST", a.sessionRequired(a.handlePostSharing)) + a.addHandler(r, "/api/v1/sharing/{rootID}", "GET", a.sessionRequired(a.handleGetSharing)) - r.HandleFunc("/api/v1/workspace", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") - r.HandleFunc("/api/v1/workspace/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") + a.addHandler(r, "/api/v1/workspace", "GET", a.sessionRequired(a.handleGetWorkspace)) + a.addHandler(r, "/api/v1/workspace/regenerate_signup_token", "POST", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)) } func (a *API) RegisterAdminRoutes(r *mux.Router) { r.HandleFunc("/api/v1/admin/users/{username}/password", a.adminRequired(a.handleAdminSetPassword)).Methods("POST") } +func (a *API) addHandler(r *mux.Router, path string, method string, f func(http.ResponseWriter, *http.Request)) { + r.HandleFunc(path, a.preHandle(f)).Methods(method) +} + +func (a *API) preHandle(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if !a.checkCSRFToken(r) { + log.Println("checkCSRFToken FAILED") + errorResponse(w, http.StatusBadRequest, nil, nil) + return + } + + handler(w, r) + } +} + +func (a *API) checkCSRFToken(r *http.Request) bool { + token := r.Header.Get(HEADER_REQUESTED_WITH) + + if token == HEADER_REQUESTED_WITH_XML { + return true + } + + return false +} + func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() parentID := query.Get("parent_id") diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index b2ff157f28a..b6b45346a8e 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -87,6 +87,7 @@ class OctoClient { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: this.token ? 'Bearer ' + this.token : '', + 'X-Requested-With': 'XMLHttpRequest', } } From d655ca3af6c63796264b2fe4c5dd311d54cda67d Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Tue, 2 Feb 2021 16:54:15 -0800 Subject: [PATCH 3/7] Add X-Requested-With header check for CSRF --- server/api/api.go | 65 +++++++++++++++++++++++++++++----------- webapp/src/octoClient.ts | 1 + 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 62f3690a4c5..320af0ef1b0 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -18,6 +18,11 @@ import ( "github.com/mattermost/focalboard/server/utils" ) +const ( + HEADER_REQUESTED_WITH = "X-Requested-With" + HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" +) + // ---------------------------------------------------------------------------------------------------- // REST APIs @@ -35,35 +40,61 @@ func (a *API) app() *app.App { } func (a *API) RegisterRoutes(r *mux.Router) { - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET") - r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") - r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") - r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") + a.addHandler(r, "/api/v1/blocks", "GET", a.sessionRequired(a.handleGetBlocks)) + a.addHandler(r, "/api/v1/blocks", "POST", a.sessionRequired(a.handlePostBlocks)) + a.addHandler(r, "/api/v1/blocks/{blockID}", "DELETE", a.sessionRequired(a.handleDeleteBlock)) + a.addHandler(r, "/api/v1/blocks/{blockID}/subtree", "GET", a.attachSession(a.handleGetSubTree, false)) - r.HandleFunc("/api/v1/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") - r.HandleFunc("/api/v1/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") + a.addHandler(r, "/api/v1/users/me", "GET", a.sessionRequired(a.handleGetMe)) + a.addHandler(r, "/api/v1/users/{userID}", "GET", a.sessionRequired(a.handleGetUser)) + a.addHandler(r, "/api/v1/users/{userID}/changepassword", "POST", a.sessionRequired(a.handleChangePassword)) - r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST") - r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST") + a.addHandler(r, "/api/v1/login", "POST", a.sessionRequired(a.handleLogin)) + a.addHandler(r, "/api/v1/register", "POST", a.sessionRequired(a.handleRegister)) - r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") - r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") + a.addHandler(r, "api/v1/files", "POST", a.sessionRequired(a.handleUploadFile)) + a.addHandler(r, "/files/{filename}", "GET", a.sessionRequired(a.handleServeFile)) - r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") - r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") + a.addHandler(r, "/api/v1/blocks/export", "GET", a.sessionRequired(a.handleExport)) + a.addHandler(r, "/api/v1/blocks/import", "POST", a.sessionRequired(a.handleImport)) - r.HandleFunc("/api/v1/sharing/{rootID}", a.sessionRequired(a.handlePostSharing)).Methods("POST") - r.HandleFunc("/api/v1/sharing/{rootID}", a.sessionRequired(a.handleGetSharing)).Methods("GET") + a.addHandler(r, "/api/v1/sharing/{rootID}", "POST", a.sessionRequired(a.handlePostSharing)) + a.addHandler(r, "/api/v1/sharing/{rootID}", "GET", a.sessionRequired(a.handleGetSharing)) - r.HandleFunc("/api/v1/workspace", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") - r.HandleFunc("/api/v1/workspace/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") + a.addHandler(r, "/api/v1/workspace", "GET", a.sessionRequired(a.handleGetWorkspace)) + a.addHandler(r, "/api/v1/workspace/regenerate_signup_token", "POST", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)) } func (a *API) RegisterAdminRoutes(r *mux.Router) { r.HandleFunc("/api/v1/admin/users/{username}/password", a.adminRequired(a.handleAdminSetPassword)).Methods("POST") } +func (a *API) addHandler(r *mux.Router, path string, method string, f func(http.ResponseWriter, *http.Request)) { + r.HandleFunc(path, a.preHandle(f)).Methods(method) +} + +func (a *API) preHandle(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if !a.checkCSRFToken(r) { + log.Println("checkCSRFToken FAILED") + errorResponse(w, http.StatusBadRequest, nil, nil) + return + } + + handler(w, r) + } +} + +func (a *API) checkCSRFToken(r *http.Request) bool { + token := r.Header.Get(HEADER_REQUESTED_WITH) + + if token == HEADER_REQUESTED_WITH_XML { + return true + } + + return false +} + func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() parentID := query.Get("parent_id") diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index b2ff157f28a..b6b45346a8e 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -87,6 +87,7 @@ class OctoClient { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: this.token ? 'Bearer ' + this.token : '', + 'X-Requested-With': 'XMLHttpRequest', } } From 519cfacaf24f9adaf6fc305d11eb25491cec3f67 Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Wed, 3 Feb 2021 10:42:06 -0800 Subject: [PATCH 4/7] Add X-Requested-With to server test --- server/client/client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/client/client.go b/server/client/client.go index 82a99d2f913..c6814f3ba64 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -65,7 +65,10 @@ type Client struct { func NewClient(url string) *Client { url = strings.TrimRight(url, "/") - return &Client{url, url + API_URL_SUFFIX, &http.Client{}, map[string]string{}} + headers := map[string]string{ + "X-Requested-With": "XMLHttpRequest", + } + return &Client{url, url + API_URL_SUFFIX, &http.Client{}, headers} } func (c *Client) DoApiGet(url string, etag string) (*http.Response, error) { From daf83691b13eb1da4854ec71abcd95f72ab7117f Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Fri, 5 Feb 2021 10:28:52 -0800 Subject: [PATCH 5/7] Refactor API to use middleware --- server/api/api.go | 54 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 320af0ef1b0..4953504b3f2 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -40,49 +40,53 @@ func (a *API) app() *app.App { } func (a *API) RegisterRoutes(r *mux.Router) { - a.addHandler(r, "/api/v1/blocks", "GET", a.sessionRequired(a.handleGetBlocks)) - a.addHandler(r, "/api/v1/blocks", "POST", a.sessionRequired(a.handlePostBlocks)) - a.addHandler(r, "/api/v1/blocks/{blockID}", "DELETE", a.sessionRequired(a.handleDeleteBlock)) - a.addHandler(r, "/api/v1/blocks/{blockID}/subtree", "GET", a.attachSession(a.handleGetSubTree, false)) + apiv1 := r.PathPrefix("/api/v1").Subrouter() + apiv1.Use(a.requireCSRFToken) - a.addHandler(r, "/api/v1/users/me", "GET", a.sessionRequired(a.handleGetMe)) - a.addHandler(r, "/api/v1/users/{userID}", "GET", a.sessionRequired(a.handleGetUser)) - a.addHandler(r, "/api/v1/users/{userID}/changepassword", "POST", a.sessionRequired(a.handleChangePassword)) + apiv1.HandleFunc("/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET") + apiv1.HandleFunc("/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") + apiv1.HandleFunc("/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") + apiv1.HandleFunc("/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") - a.addHandler(r, "/api/v1/login", "POST", a.sessionRequired(a.handleLogin)) - a.addHandler(r, "/api/v1/register", "POST", a.sessionRequired(a.handleRegister)) + apiv1.HandleFunc("/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") + apiv1.HandleFunc("/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") + apiv1.HandleFunc("/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") - a.addHandler(r, "api/v1/files", "POST", a.sessionRequired(a.handleUploadFile)) - a.addHandler(r, "/files/{filename}", "GET", a.sessionRequired(a.handleServeFile)) + apiv1.HandleFunc("/login", a.handleLogin).Methods("POST") + apiv1.HandleFunc("/register", a.handleRegister).Methods("POST") - a.addHandler(r, "/api/v1/blocks/export", "GET", a.sessionRequired(a.handleExport)) - a.addHandler(r, "/api/v1/blocks/import", "POST", a.sessionRequired(a.handleImport)) + apiv1.HandleFunc("/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") + apiv1.HandleFunc("/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") - a.addHandler(r, "/api/v1/sharing/{rootID}", "POST", a.sessionRequired(a.handlePostSharing)) - a.addHandler(r, "/api/v1/sharing/{rootID}", "GET", a.sessionRequired(a.handleGetSharing)) + apiv1.HandleFunc("/sharing/{rootID}", a.sessionRequired(a.handlePostSharing)).Methods("POST") + apiv1.HandleFunc("/sharing/{rootID}", a.sessionRequired(a.handleGetSharing)).Methods("GET") - a.addHandler(r, "/api/v1/workspace", "GET", a.sessionRequired(a.handleGetWorkspace)) - a.addHandler(r, "/api/v1/workspace/regenerate_signup_token", "POST", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)) + apiv1.HandleFunc("/workspace", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") + apiv1.HandleFunc("/workspace/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") + + // Files API + + files := r.PathPrefix("/files/").Subrouter() + files.Use(a.requireCSRFToken) + + files.HandleFunc("/", a.sessionRequired(a.handleUploadFile)).Methods("POST") + files.HandleFunc("/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") } func (a *API) RegisterAdminRoutes(r *mux.Router) { r.HandleFunc("/api/v1/admin/users/{username}/password", a.adminRequired(a.handleAdminSetPassword)).Methods("POST") } -func (a *API) addHandler(r *mux.Router, path string, method string, f func(http.ResponseWriter, *http.Request)) { - r.HandleFunc(path, a.preHandle(f)).Methods(method) -} - -func (a *API) preHandle(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { +func (a *API) requireCSRFToken(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !a.checkCSRFToken(r) { log.Println("checkCSRFToken FAILED") errorResponse(w, http.StatusBadRequest, nil, nil) return } - handler(w, r) - } + next.ServeHTTP(w, r) + }) } func (a *API) checkCSRFToken(r *http.Request) bool { From c484eb8c43eec1128f6a5ae8432181e800ca9a3c Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Fri, 5 Feb 2021 10:45:28 -0800 Subject: [PATCH 6/7] Don't require CSRF token for get files --- server/api/api.go | 7 +++---- webapp/src/octoClient.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 4953504b3f2..41d3d85e05f 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -55,6 +55,8 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/login", a.handleLogin).Methods("POST") apiv1.HandleFunc("/register", a.handleRegister).Methods("POST") + apiv1.HandleFunc("/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") + apiv1.HandleFunc("/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") apiv1.HandleFunc("/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") @@ -64,12 +66,9 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/workspace", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") apiv1.HandleFunc("/workspace/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") - // Files API + // Get Files API files := r.PathPrefix("/files/").Subrouter() - files.Use(a.requireCSRFToken) - - files.HandleFunc("/", a.sessionRequired(a.handleUploadFile)).Methods("POST") files.HandleFunc("/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") } diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index b6b45346a8e..8ae7fa538d4 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -232,14 +232,14 @@ class OctoClient { formData.append('file', file) try { + const headers = this.headers() as Record + + // TIPTIP: Leave out Content-Type here, it will be automatically set by the browser + delete headers['Content-Type'] + const response = await fetch(this.serverUrl + '/api/v1/files', { method: 'POST', - - // TIPTIP: Leave out Content-Type here, it will be automatically set by the browser - headers: { - Accept: 'application/json', - Authorization: this.token ? 'Bearer ' + this.token : '', - }, + headers, body: formData, }) if (response.status !== 200) { From 1957e1dbaa898a866ac72acabdabd618a1b66edb Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Fri, 5 Feb 2021 11:22:56 -0800 Subject: [PATCH 7/7] cleanup --- server/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/api.go b/server/api/api.go index 41d3d85e05f..017487ca976 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -68,7 +68,7 @@ func (a *API) RegisterRoutes(r *mux.Router) { // Get Files API - files := r.PathPrefix("/files/").Subrouter() + files := r.PathPrefix("/files").Subrouter() files.HandleFunc("/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") }