@ -371,22 +371,14 @@ func (s *Server) authorizeRequest(w http.ResponseWriter, r *http.Request) (ok bo
// which protects the handler using gorilla csrf.
// which protects the handler using gorilla csrf.
func ( s * Server ) serveLoginAPI ( w http . ResponseWriter , r * http . Request ) {
func ( s * Server ) serveLoginAPI ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "X-CSRF-Token" , csrf . Token ( r ) )
w . Header ( ) . Set ( "X-CSRF-Token" , csrf . Token ( r ) )
if r . URL . Path != "/api/data" { // only endpoint allowed for login client
switch {
http . Error ( w , "invalid endpoint" , http . StatusNotFound )
case r . URL . Path == "/api/data" && r . Method == httpm . GET :
return
}
switch r . Method {
case httpm . GET :
// TODO(soniaappasamy): we may want a minimal node data response here
s . serveGetNodeData ( w , r )
s . serveGetNodeData ( w , r )
return
case r . URL . Path == "/api/up" && r . Method == httpm . POST :
case httpm . POST :
s . serveTailscaleUp ( w , r )
// TODO(will): refactor to expose only a dedicated login method
default :
s . servePostNodeUpdate ( w , r )
http . Error ( w , "invalid endpoint or method" , http . StatusNotFound )
return
}
}
http . Error ( w , "invalid endpoint" , http . StatusNotFound )
return
}
}
type authType string
type authType string
@ -648,19 +640,11 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
type nodeUpdate struct {
type nodeUpdate struct {
AdvertiseRoutes string
AdvertiseRoutes string
AdvertiseExitNode bool
AdvertiseExitNode bool
Reauthenticate bool
ForceLogout bool
}
}
func ( s * Server ) servePostNodeUpdate ( w http . ResponseWriter , r * http . Request ) {
func ( s * Server ) servePostNodeUpdate ( w http . ResponseWriter , r * http . Request ) {
defer r . Body . Close ( )
defer r . Body . Close ( )
st , err := s . lc . Status ( r . Context ( ) )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
var postData nodeUpdate
var postData nodeUpdate
type mi map [ string ] any
type mi map [ string ] any
if err := json . NewDecoder ( r . Body ) . Decode ( & postData ) ; err != nil {
if err := json . NewDecoder ( r . Body ) . Decode ( & postData ) ; err != nil {
@ -706,46 +690,30 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) {
}
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
var reauth , logout bool
io . WriteString ( w , "{}" )
if postData . Reauthenticate {
reauth = true
}
if postData . ForceLogout {
logout = true
}
s . logf ( "tailscaleUp(reauth=%v, logout=%v) ..." , reauth , logout )
url , err := s . tailscaleUp ( r . Context ( ) , st , postData )
s . logf ( "tailscaleUp = (URL %v, %v)" , url != "" , err )
if err != nil {
w . WriteHeader ( http . StatusInternalServerError )
json . NewEncoder ( w ) . Encode ( mi { "error" : err . Error ( ) } )
return
}
if url != "" {
json . NewEncoder ( w ) . Encode ( mi { "url" : url } )
} else {
io . WriteString ( w , "{}" )
}
}
}
func ( s * Server ) tailscaleUp ( ctx context . Context , st * ipnstate . Status , postData nodeUpdate ) ( authURL string , retErr error ) {
// tailscaleUp starts the daemon with the provided options.
if postData . ForceLogout {
// If reauthentication has been requested, an authURL is returned to complete device registration.
if err := s . lc . Logout ( ctx ) ; err != nil {
func ( s * Server ) tailscaleUp ( ctx context . Context , st * ipnstate . Status , opt tailscaleUpOptions ) ( authURL string , retErr error ) {
return "" , fmt . Errorf ( "Logout error: %w" , err )
}
return "" , nil
}
origAuthURL := st . AuthURL
origAuthURL := st . AuthURL
isRunning := st . BackendState == ipn . Running . String ( )
isRunning := st . BackendState == ipn . Running . String ( )
forceReauth := postData . Reauthenticate
if ! opt . Reauthenticate {
if ! forceReau th {
switch {
if origAuthURL != "" {
case origAuthURL != "" :
return origAuthURL , nil
return origAuthURL , nil
}
case isRunning :
if isRunning {
return "" , nil
return "" , nil
case st . BackendState == ipn . Stopped . String ( ) :
// stopped and not reauthenticating, so just start running
_ , err := s . lc . EditPrefs ( ctx , & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
WantRunning : true ,
} ,
WantRunningSet : true ,
} )
return "" , err
}
}
}
}
@ -767,7 +735,7 @@ func (s *Server) tailscaleUp(ctx context.Context, st *ipnstate.Status, postData
if ! isRunning {
if ! isRunning {
s . lc . Start ( ctx , ipn . Options { } )
s . lc . Start ( ctx , ipn . Options { } )
}
}
if forceReauth {
if opt. Reauthenticate {
s . lc . StartLoginInteractive ( ctx )
s . lc . StartLoginInteractive ( ctx )
}
}
} ( )
} ( )
@ -787,6 +755,47 @@ func (s *Server) tailscaleUp(ctx context.Context, st *ipnstate.Status, postData
}
}
}
}
type tailscaleUpOptions struct {
// If true, force reauthentication of the client.
// Otherwise simply reconnect, the same as running `tailscale up`.
Reauthenticate bool
}
// serveTailscaleUp serves requests to /api/up.
// If the user needs to authenticate, an authURL is provided in the response.
func ( s * Server ) serveTailscaleUp ( w http . ResponseWriter , r * http . Request ) {
defer r . Body . Close ( )
st , err := s . lc . Status ( r . Context ( ) )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
var opt tailscaleUpOptions
type mi map [ string ] any
if err := json . NewDecoder ( r . Body ) . Decode ( & opt ) ; err != nil {
w . WriteHeader ( 400 )
json . NewEncoder ( w ) . Encode ( mi { "error" : err . Error ( ) } )
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
s . logf ( "tailscaleUp(reauth=%v) ..." , opt . Reauthenticate )
url , err := s . tailscaleUp ( r . Context ( ) , st , opt )
s . logf ( "tailscaleUp = (URL %v, %v)" , url != "" , err )
if err != nil {
w . WriteHeader ( http . StatusInternalServerError )
json . NewEncoder ( w ) . Encode ( mi { "error" : err . Error ( ) } )
return
}
if url != "" {
json . NewEncoder ( w ) . Encode ( mi { "url" : url } )
} else {
io . WriteString ( w , "{}" )
}
}
// proxyRequestToLocalAPI proxies the web API request to the localapi.
// proxyRequestToLocalAPI proxies the web API request to the localapi.
//
//
// The web API request path is expected to exactly match a localapi path,
// The web API request path is expected to exactly match a localapi path,