@@ -39,7 +39,17 @@ 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+ // JavaScript in browsers cannot set this header, per the Fetch Spec
50+ // (https://fetch.spec.whatwg.org/).
51+ secHeaderName = "Sec-Golink"
52+ )
4353
4454var (
4555 verbose = flag .Bool ("verbose" , false , "be verbose" )
@@ -259,6 +269,14 @@ type homeData struct {
259269 Short string
260270 Long string
261271 Clicks []visitData
272+ XSRF string
273+ }
274+
275+ // deleteData is the data used by the deleteTmpl template.
276+ type deleteData struct {
277+ Short string
278+ Long string
279+ XSRF string
262280}
263281
264282var xsrfKey string
@@ -416,10 +434,16 @@ func serveHome(w http.ResponseWriter, r *http.Request, short string) {
416434 }
417435 }
418436
437+ cu , err := currentUser (r )
438+ if err != nil {
439+ http .Error (w , err .Error (), http .StatusInternalServerError )
440+ return
441+ }
419442 homeTmpl .Execute (w , homeData {
420443 Short : short ,
421444 Long : long ,
422445 Clicks : clicks ,
446+ XSRF : xsrftoken .Generate (xsrfKey , cu .login , newShortName ),
423447 })
424448}
425449
@@ -739,6 +763,11 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
739763 return
740764 }
741765
766+ // Deletion by CLI has never worked because it has always required the XSRF
767+ // token. (Refer to commit c7ac33d04c33743606f6224009a5c73aa0b8dec0.) If we
768+ // want to enable deletion via CLI and to honor allowUnknownUsers for
769+ // deletion, we could change the below to a call to isRequestAuthorized. For
770+ // now, always require the XSRF token, thus maintaining the status quo.
742771 if ! xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , cu .login , short ) {
743772 http .Error (w , "invalid XSRF token" , http .StatusBadRequest )
744773 return
@@ -750,7 +779,11 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
750779 }
751780 deleteLinkStats (link )
752781
753- deleteTmpl .Execute (w , link )
782+ deleteTmpl .Execute (w , deleteData {
783+ Short : link .Short ,
784+ Long : link .Long ,
785+ XSRF : xsrftoken .Generate (xsrfKey , cu .login , newShortName ),
786+ })
754787}
755788
756789// serveSave handles requests to save or update a Link. Both short name and
@@ -787,6 +820,11 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
787820 return
788821 }
789822
823+ if ! isRequestAuthorized (r , cu , short ) {
824+ http .Error (w , "invalid XSRF token" , http .StatusBadRequest )
825+ return
826+ }
827+
790828 // allow transferring ownership to valid users. If empty, set owner to current user.
791829 owner := r .FormValue ("owner" )
792830 if owner != "" {
@@ -886,8 +924,7 @@ func restoreLastSnapshot() error {
886924 _ , err := db .Load (link .Short )
887925 if err == nil {
888926 continue // exists
889- }
890- if err != nil && ! errors .Is (err , fs .ErrNotExist ) {
927+ } else if ! errors .Is (err , fs .ErrNotExist ) {
891928 return err
892929 }
893930 if err := db .Save (link ); err != nil {
@@ -923,3 +960,21 @@ func resolveLink(link *url.URL) (*url.URL, error) {
923960 }
924961 return dst , err
925962}
963+
964+ func isRequestAuthorized (r * http.Request , u user , short string ) bool {
965+ if * allowUnknownUsers {
966+ return true
967+ }
968+ if r .Header .Get (secHeaderName ) != "" {
969+ return true
970+ }
971+
972+ // If the request is to create a new link, test the XSRF token against
973+ // newShortName instead of short.
974+ tokenShortName := short
975+ _ , err := db .Load (short )
976+ if r .URL .Path == "/" && r .Method == http .MethodPost && errors .Is (err , fs .ErrNotExist ) {
977+ tokenShortName = newShortName
978+ }
979+ return xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , u .login , tokenShortName )
980+ }
0 commit comments