@@ -39,7 +39,20 @@ import (
3939 "tailscale.com/util/dnsname"
4040)
4141
42- const defaultHostname = "go"
42+ const (
43+ defaultHostname = "go"
44+
45+ // Used as a placeholder short name for generating the XSRF defense token,
46+ // when creating new links.
47+ newShortName = ".new"
48+
49+ // If the caller sends this header set to a non-empty value, we will allow
50+ // them to make the call even without an XSRF token. JavaScript in browser
51+ // cannot set this header, per the [Fetch Spec].
52+ //
53+ // [Fetch Spec]: https://fetch.spec.whatwg.org
54+ secHeaderName = "Sec-Golink"
55+ )
4356
4457var (
4558 verbose = flag .Bool ("verbose" , false , "be verbose" )
@@ -254,11 +267,19 @@ type visitData struct {
254267 NumClicks int
255268}
256269
257- // homeData is the data used by the homeTmpl template .
270+ // homeData is the data used by homeTmpl.
258271type homeData struct {
259272 Short string
260273 Long string
261274 Clicks []visitData
275+ XSRF string
276+ }
277+
278+ // deleteData is the data used by deleteTmpl.
279+ type deleteData struct {
280+ Short string
281+ Long string
282+ XSRF string
262283}
263284
264285var xsrfKey string
@@ -416,10 +437,16 @@ func serveHome(w http.ResponseWriter, r *http.Request, short string) {
416437 }
417438 }
418439
440+ cu , err := currentUser (r )
441+ if err != nil {
442+ http .Error (w , err .Error (), http .StatusInternalServerError )
443+ return
444+ }
419445 homeTmpl .Execute (w , homeData {
420446 Short : short ,
421447 Long : long ,
422448 Clicks : clicks ,
449+ XSRF : xsrftoken .Generate (xsrfKey , cu .login , newShortName ),
423450 })
424451}
425452
@@ -739,6 +766,11 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
739766 return
740767 }
741768
769+ // Deletion by CLI has never worked because it has always required the XSRF
770+ // token. (Refer to commit c7ac33d04c33743606f6224009a5c73aa0b8dec0.) If we
771+ // want to enable deletion via CLI and to honor allowUnknownUsers for
772+ // deletion, we could change the below to a call to isRequestAuthorized. For
773+ // now, always require the XSRF token, thus maintaining the status quo.
742774 if ! xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , cu .login , short ) {
743775 http .Error (w , "invalid XSRF token" , http .StatusBadRequest )
744776 return
@@ -750,7 +782,11 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
750782 }
751783 deleteLinkStats (link )
752784
753- deleteTmpl .Execute (w , link )
785+ deleteTmpl .Execute (w , deleteData {
786+ Short : link .Short ,
787+ Long : link .Long ,
788+ XSRF : xsrftoken .Generate (xsrfKey , cu .login , newShortName ),
789+ })
754790}
755791
756792// serveSave handles requests to save or update a Link. Both short name and
@@ -787,6 +823,11 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
787823 return
788824 }
789825
826+ if ! isRequestAuthorized (r , cu , short ) {
827+ http .Error (w , "invalid XSRF token" , http .StatusBadRequest )
828+ return
829+ }
830+
790831 // allow transferring ownership to valid users. If empty, set owner to current user.
791832 owner := r .FormValue ("owner" )
792833 if owner != "" {
@@ -886,8 +927,7 @@ func restoreLastSnapshot() error {
886927 _ , err := db .Load (link .Short )
887928 if err == nil {
888929 continue // exists
889- }
890- if err != nil && ! errors .Is (err , fs .ErrNotExist ) {
930+ } else if ! errors .Is (err , fs .ErrNotExist ) {
891931 return err
892932 }
893933 if err := db .Save (link ); err != nil {
@@ -923,3 +963,21 @@ func resolveLink(link *url.URL) (*url.URL, error) {
923963 }
924964 return dst , err
925965}
966+
967+ func isRequestAuthorized (r * http.Request , u user , short string ) bool {
968+ if * allowUnknownUsers {
969+ return true
970+ }
971+ if r .Header .Get (secHeaderName ) != "" {
972+ return true
973+ }
974+
975+ // If the request is to create a new link, test the XSRF token against
976+ // newShortName instead of short.
977+ tokenShortName := short
978+ _ , err := db .Load (short )
979+ if r .URL .Path == "/" && r .Method == http .MethodPost && errors .Is (err , fs .ErrNotExist ) {
980+ tokenShortName = newShortName
981+ }
982+ return xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , u .login , tokenShortName )
983+ }
0 commit comments