Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add X-Requested-With header to prevent CSRF #48

Merged
merged 11 commits into from
Feb 5, 2021
65 changes: 48 additions & 17 deletions server/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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/#", a.handleLogin).Methods("POST")
r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST")
a.addHandler(r, "/api/v1/#", "POST", a.sessionRequired(a.handleLogin))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are adding sessionRequired to the login and register ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, fixed that. Much easier to see with the middleware pattern.

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_#_token", a.sessionRequired(a.handlePostWorkspaceRegenerate#Token)).Methods("POST")
a.addHandler(r, "/api/v1/workspace", "GET", a.sessionRequired(a.handleGetWorkspace))
a.addHandler(r, "/api/v1/workspace/regenerate_#_token", "POST", a.sessionRequired(a.handlePostWorkspaceRegenerate#Token))
}

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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a look like a middleware

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Jesus! I knew there was a cleaner way. Refactored to use Middleware.

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")
Expand Down
5 changes: 4 additions & 1 deletion server/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions webapp/src/octoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class OctoClient {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
'X-Requested-With': 'XMLHttpRequest',
}
}

Expand Down