mirror of https://github.com/tailscale/tailscale/
client/web: add csrf protection to web client api
Adds csrf protection and hooks up an initial POST request from the React web client. Updates tailscale/corp#13775 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>pull/8994/head
parent
77ff705545
commit
077bbb8403
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"tailscale.com/util/httpm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type api struct {
|
||||||
|
s *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP serves requests for the web client api.
|
||||||
|
// It should only be called by Server.ServeHTTP, via Server.apiHandler,
|
||||||
|
// which protects the handler using gorilla csrf.
|
||||||
|
func (a *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
||||||
|
user, err := authorize(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := strings.TrimPrefix(r.URL.Path, "/api")
|
||||||
|
switch path {
|
||||||
|
case "/data":
|
||||||
|
switch r.Method {
|
||||||
|
case httpm.GET:
|
||||||
|
a.s.serveGetNodeDataJSON(w, r, user)
|
||||||
|
case httpm.POST:
|
||||||
|
a.s.servePostNodeUpdate(w, r)
|
||||||
|
default:
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "invalid endpoint", http.StatusNotFound)
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
let csrfToken: string
|
||||||
|
|
||||||
|
// apiFetch wraps the standard JS fetch function
|
||||||
|
// with csrf header management.
|
||||||
|
export function apiFetch(
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init?: RequestInit | undefined
|
||||||
|
): Promise<Response> {
|
||||||
|
return fetch(input, {
|
||||||
|
...init,
|
||||||
|
headers: withCsrfToken(init?.headers),
|
||||||
|
}).then((r) => {
|
||||||
|
updateCsrfToken(r)
|
||||||
|
if (!r.ok) {
|
||||||
|
return r.text().then((err) => {
|
||||||
|
throw new Error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function withCsrfToken(h?: HeadersInit): HeadersInit {
|
||||||
|
return { ...h, "X-CSRF-Token": csrfToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCsrfToken(r: Response) {
|
||||||
|
const tok = r.headers.get("X-CSRF-Token")
|
||||||
|
if (tok) {
|
||||||
|
csrfToken = tok
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue