github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/lib/oauthutil/oauthutil.go (about)

     1  package oauthutil
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"html/template"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/rclone/rclone/fs"
    16  	"github.com/rclone/rclone/fs/config"
    17  	"github.com/rclone/rclone/fs/config/configmap"
    18  	"github.com/rclone/rclone/fs/fshttp"
    19  	"github.com/rclone/rclone/lib/random"
    20  	"github.com/skratchdot/open-golang/open"
    21  	"golang.org/x/oauth2"
    22  )
    23  
    24  const (
    25  	// TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization
    26  	// code should be returned in the title bar of the browser, with the page text
    27  	// prompting the user to copy the code and paste it in the application.
    28  	TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob"
    29  
    30  	// bindPort is the port that we bind the local webserver to
    31  	bindPort = "53682"
    32  
    33  	// bindAddress is binding for local webserver when active
    34  	bindAddress = "127.0.0.1:" + bindPort
    35  
    36  	// RedirectURL is redirect to local webserver when active
    37  	RedirectURL = "http://" + bindAddress + "/"
    38  
    39  	// RedirectPublicURL is redirect to local webserver when active with public name
    40  	RedirectPublicURL = "http://localhost.rclone.org:" + bindPort + "/"
    41  
    42  	// RedirectLocalhostURL is redirect to local webserver when active with localhost
    43  	RedirectLocalhostURL = "http://localhost:" + bindPort + "/"
    44  
    45  	// RedirectPublicSecureURL is a public https URL which
    46  	// redirects to the local webserver
    47  	RedirectPublicSecureURL = "https://oauth.rclone.org/"
    48  
    49  	// AuthResponseTemplate is a template to handle the redirect URL for oauth requests
    50  	AuthResponseTemplate = `<!DOCTYPE html>
    51  <html lang="en">
    52  <head>
    53  <meta charset="utf-8">
    54  <title>{{ if .OK }}Success!{{ else }}Failure!{{ end }}</title>
    55  </head>
    56  <body>
    57  <h1>{{ if .OK }}Success!{{ else }}Failure!{{ end }}</h1>
    58  <hr>
    59  <pre style="width: 750px; white-space: pre-wrap;">
    60  {{ if eq .OK false }}
    61  Error: {{ .Name }}<br>
    62  {{ if .Description }}Description: {{ .Description }}<br>{{ end }}
    63  {{ if .Code }}Code: {{ .Code }}<br>{{ end }}
    64  {{ if .HelpURL }}Look here for help: <a href="{{ .HelpURL }}">{{ .HelpURL }}</a><br>{{ end }}
    65  {{ else }}
    66  All done. Please go back to rclone.
    67  {{ end }}
    68  </pre>
    69  </body>
    70  </html>
    71  `
    72  )
    73  
    74  // oldToken contains an end-user's tokens.
    75  // This is the data you must store to persist authentication.
    76  //
    77  // From the original code.google.com/p/goauth2/oauth package - used
    78  // for backwards compatibility in the rclone config file
    79  type oldToken struct {
    80  	AccessToken  string
    81  	RefreshToken string
    82  	Expiry       time.Time
    83  }
    84  
    85  // GetToken returns the token saved in the config file under
    86  // section name.
    87  func GetToken(name string, m configmap.Mapper) (*oauth2.Token, error) {
    88  	tokenString, ok := m.Get(config.ConfigToken)
    89  	if !ok || tokenString == "" {
    90  		return nil, errors.New("empty token found - please run rclone config again")
    91  	}
    92  	token := new(oauth2.Token)
    93  	err := json.Unmarshal([]byte(tokenString), token)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	// if has data then return it
    98  	if token.AccessToken != "" {
    99  		return token, nil
   100  	}
   101  	// otherwise try parsing as oldToken
   102  	oldtoken := new(oldToken)
   103  	err = json.Unmarshal([]byte(tokenString), oldtoken)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	// Fill in result into new token
   108  	token.AccessToken = oldtoken.AccessToken
   109  	token.RefreshToken = oldtoken.RefreshToken
   110  	token.Expiry = oldtoken.Expiry
   111  	// Save new format in config file
   112  	err = PutToken(name, m, token, false)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return token, nil
   117  }
   118  
   119  // PutToken stores the token in the config file
   120  //
   121  // This saves the config file if it changes
   122  func PutToken(name string, m configmap.Mapper, token *oauth2.Token, newSection bool) error {
   123  	tokenBytes, err := json.Marshal(token)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	tokenString := string(tokenBytes)
   128  	old, ok := m.Get(config.ConfigToken)
   129  	if !ok || tokenString != old {
   130  		err = config.SetValueAndSave(name, config.ConfigToken, tokenString)
   131  		if newSection && err != nil {
   132  			fs.Debugf(name, "Added new token to config, still needs to be saved")
   133  		} else if err != nil {
   134  			fs.Errorf(nil, "Failed to save new token in config file: %v", err)
   135  		} else {
   136  			fs.Debugf(name, "Saved new token in config file")
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  // TokenSource stores updated tokens in the config file
   143  type TokenSource struct {
   144  	mu          sync.Mutex
   145  	name        string
   146  	m           configmap.Mapper
   147  	tokenSource oauth2.TokenSource
   148  	token       *oauth2.Token
   149  	config      *oauth2.Config
   150  	ctx         context.Context
   151  	expiryTimer *time.Timer // signals whenever the token expires
   152  }
   153  
   154  // If token has expired then first try re-reading it from the config
   155  // file in case a concurrently running rclone has updated it already
   156  func (ts *TokenSource) reReadToken() bool {
   157  	tokenString, err := config.FileGetFresh(ts.name, config.ConfigToken)
   158  	if err != nil {
   159  		fs.Debugf(ts.name, "Failed to read token out of config file: %v", err)
   160  		return false
   161  	}
   162  	newToken := new(oauth2.Token)
   163  	err = json.Unmarshal([]byte(tokenString), newToken)
   164  	if err != nil {
   165  		fs.Debugf(ts.name, "Failed to parse token out of config file: %v", err)
   166  		return false
   167  	}
   168  	if !newToken.Valid() {
   169  		fs.Debugf(ts.name, "Loaded invalid token from config file - ignoring")
   170  		return false
   171  	}
   172  	fs.Debugf(ts.name, "Loaded fresh token from config file")
   173  	ts.token = newToken
   174  	ts.tokenSource = nil // invalidate since we changed the token
   175  	return true
   176  }
   177  
   178  // Token returns a token or an error.
   179  // Token must be safe for concurrent use by multiple goroutines.
   180  // The returned Token must not be modified.
   181  //
   182  // This saves the token in the config file if it has changed
   183  func (ts *TokenSource) Token() (*oauth2.Token, error) {
   184  	ts.mu.Lock()
   185  	defer ts.mu.Unlock()
   186  	var (
   187  		token   *oauth2.Token
   188  		err     error
   189  		changed = false
   190  	)
   191  	const maxTries = 5
   192  
   193  	// Try getting the token a few times
   194  	for i := 1; i <= maxTries; i++ {
   195  		// Try reading the token from the config file in case it has
   196  		// been updated by a concurrent rclone process
   197  		if !ts.token.Valid() {
   198  			if ts.reReadToken() {
   199  				changed = true
   200  			}
   201  		}
   202  
   203  		// Make a new token source if required
   204  		if ts.tokenSource == nil {
   205  			ts.tokenSource = ts.config.TokenSource(ts.ctx, ts.token)
   206  		}
   207  
   208  		token, err = ts.tokenSource.Token()
   209  		if err == nil {
   210  			break
   211  		}
   212  		fs.Debugf(ts.name, "Token refresh failed try %d/%d: %v", i, maxTries, err)
   213  		time.Sleep(1 * time.Second)
   214  	}
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	changed = changed || (*token != *ts.token)
   219  	ts.token = token
   220  	if changed {
   221  		// Bump on the expiry timer if it is set
   222  		if ts.expiryTimer != nil {
   223  			ts.expiryTimer.Reset(ts.timeToExpiry())
   224  		}
   225  		err = PutToken(ts.name, ts.m, token, false)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  	}
   230  	return token, nil
   231  }
   232  
   233  // Invalidate invalidates the token
   234  func (ts *TokenSource) Invalidate() {
   235  	ts.mu.Lock()
   236  	ts.token.AccessToken = ""
   237  	ts.mu.Unlock()
   238  }
   239  
   240  // timeToExpiry returns how long until the token expires
   241  //
   242  // Call with the lock held
   243  func (ts *TokenSource) timeToExpiry() time.Duration {
   244  	t := ts.token
   245  	if t == nil {
   246  		return 0
   247  	}
   248  	if t.Expiry.IsZero() {
   249  		return 3e9 * time.Second // ~95 years
   250  	}
   251  	return t.Expiry.Sub(time.Now())
   252  }
   253  
   254  // OnExpiry returns a channel which has the time written to it when
   255  // the token expires.  Note that there is only one channel so if
   256  // attaching multiple go routines it will only signal to one of them.
   257  func (ts *TokenSource) OnExpiry() <-chan time.Time {
   258  	ts.mu.Lock()
   259  	defer ts.mu.Unlock()
   260  	if ts.expiryTimer == nil {
   261  		ts.expiryTimer = time.NewTimer(ts.timeToExpiry())
   262  	}
   263  	return ts.expiryTimer.C
   264  }
   265  
   266  // Check interface satisfied
   267  var _ oauth2.TokenSource = (*TokenSource)(nil)
   268  
   269  // Context returns a context with our HTTP Client baked in for oauth2
   270  func Context(client *http.Client) context.Context {
   271  	return context.WithValue(context.Background(), oauth2.HTTPClient, client)
   272  }
   273  
   274  // overrideCredentials sets the ClientID and ClientSecret from the
   275  // config file if they are not blank.
   276  // If any value is overridden, true is returned.
   277  // the origConfig is copied
   278  func overrideCredentials(name string, m configmap.Mapper, origConfig *oauth2.Config) (newConfig *oauth2.Config, changed bool) {
   279  	newConfig = new(oauth2.Config)
   280  	*newConfig = *origConfig
   281  	changed = false
   282  	ClientID, ok := m.Get(config.ConfigClientID)
   283  	if ok && ClientID != "" {
   284  		newConfig.ClientID = ClientID
   285  		changed = true
   286  	}
   287  	ClientSecret, ok := m.Get(config.ConfigClientSecret)
   288  	if ok && ClientSecret != "" {
   289  		newConfig.ClientSecret = ClientSecret
   290  		changed = true
   291  	}
   292  	AuthURL, ok := m.Get(config.ConfigAuthURL)
   293  	if ok && AuthURL != "" {
   294  		newConfig.Endpoint.AuthURL = AuthURL
   295  		changed = true
   296  	}
   297  	TokenURL, ok := m.Get(config.ConfigTokenURL)
   298  	if ok && TokenURL != "" {
   299  		newConfig.Endpoint.TokenURL = TokenURL
   300  		changed = true
   301  	}
   302  	return newConfig, changed
   303  }
   304  
   305  // NewClientWithBaseClient gets a token from the config file and
   306  // configures a Client with it.  It returns the client and a
   307  // TokenSource which Invalidate may need to be called on.  It uses the
   308  // httpClient passed in as the base client.
   309  func NewClientWithBaseClient(name string, m configmap.Mapper, config *oauth2.Config, baseClient *http.Client) (*http.Client, *TokenSource, error) {
   310  	config, _ = overrideCredentials(name, m, config)
   311  	token, err := GetToken(name, m)
   312  	if err != nil {
   313  		return nil, nil, err
   314  	}
   315  
   316  	// Set our own http client in the context
   317  	ctx := Context(baseClient)
   318  
   319  	// Wrap the TokenSource in our TokenSource which saves changed
   320  	// tokens in the config file
   321  	ts := &TokenSource{
   322  		name:   name,
   323  		m:      m,
   324  		token:  token,
   325  		config: config,
   326  		ctx:    ctx,
   327  	}
   328  	return oauth2.NewClient(ctx, ts), ts, nil
   329  
   330  }
   331  
   332  // NewClient gets a token from the config file and configures a Client
   333  // with it.  It returns the client and a TokenSource which Invalidate may need to be called on
   334  func NewClient(name string, m configmap.Mapper, oauthConfig *oauth2.Config) (*http.Client, *TokenSource, error) {
   335  	return NewClientWithBaseClient(name, m, oauthConfig, fshttp.NewClient(fs.Config))
   336  }
   337  
   338  // AuthResult is returned from the web server after authorization
   339  // success or failure
   340  type AuthResult struct {
   341  	OK          bool // Failure or Success?
   342  	Name        string
   343  	Description string
   344  	Code        string
   345  	HelpURL     string
   346  	Form        url.Values // the complete contents of the form
   347  	Err         error      // any underlying error to report
   348  }
   349  
   350  // Error satisfies the error interface so AuthResult can be used as an error
   351  func (ar *AuthResult) Error() string {
   352  	status := "Error"
   353  	if ar.OK {
   354  		status = "OK"
   355  	}
   356  	return fmt.Sprintf("%s: %s\nCode: %q\nDescription: %s\nHelp: %s",
   357  		status, ar.Name, ar.Code, ar.Description, ar.HelpURL)
   358  }
   359  
   360  // Config does the initial creation of the token
   361  //
   362  // It may run an internal webserver to receive the results
   363  func Config(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
   364  	return doConfig(id, name, m, config, true, nil, opts)
   365  }
   366  
   367  // CheckAuthFn is called when a good Auth has been received
   368  type CheckAuthFn func(*oauth2.Config, *AuthResult) error
   369  
   370  // ConfigWithCallback does the initial creation of the token
   371  //
   372  // It may run an internal webserver to receive the results
   373  //
   374  // When the AuthResult is known the checkAuth function is called if set
   375  func ConfigWithCallback(id, name string, m configmap.Mapper, config *oauth2.Config, checkAuth CheckAuthFn, opts ...oauth2.AuthCodeOption) error {
   376  	return doConfig(id, name, m, config, true, checkAuth, opts)
   377  }
   378  
   379  // ConfigNoOffline does the same as Config but does not pass the
   380  // "access_type=offline" parameter.
   381  func ConfigNoOffline(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
   382  	return doConfig(id, name, m, config, false, nil, opts)
   383  }
   384  
   385  func doConfig(id, name string, m configmap.Mapper, oauthConfig *oauth2.Config, offline bool, checkAuth CheckAuthFn, opts []oauth2.AuthCodeOption) error {
   386  	oauthConfig, changed := overrideCredentials(name, m, oauthConfig)
   387  	authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize)
   388  	authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize"
   389  	authorizeNoAutoBrowserValue, ok := m.Get(config.ConfigAuthNoBrowser)
   390  	authorizeNoAutoBrowser := ok && authorizeNoAutoBrowserValue != ""
   391  
   392  	// See if already have a token
   393  	tokenString, ok := m.Get("token")
   394  	if ok && tokenString != "" {
   395  		fmt.Printf("Already have a token - refresh?\n")
   396  		if !config.ConfirmWithConfig(m, "config_refresh_token", true) {
   397  			return nil
   398  		}
   399  	}
   400  
   401  	// Ask the user whether they are using a local machine
   402  	isLocal := func() bool {
   403  		fmt.Printf("Use auto config?\n")
   404  		fmt.Printf(" * Say Y if not sure\n")
   405  		fmt.Printf(" * Say N if you are working on a remote or headless machine\n")
   406  		return config.ConfirmWithConfig(m, "config_is_local", true)
   407  	}
   408  
   409  	// Detect whether we should use internal web server
   410  	useWebServer := false
   411  	switch oauthConfig.RedirectURL {
   412  	case TitleBarRedirectURL:
   413  		useWebServer = authorizeOnly
   414  		if !authorizeOnly {
   415  			useWebServer = isLocal()
   416  		}
   417  		if useWebServer {
   418  			// copy the config and set to use the internal webserver
   419  			configCopy := *oauthConfig
   420  			oauthConfig = &configCopy
   421  			oauthConfig.RedirectURL = RedirectURL
   422  		}
   423  	default:
   424  		if changed {
   425  			fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
   426  		}
   427  		useWebServer = true
   428  		if authorizeOnly {
   429  			break
   430  		}
   431  		if !isLocal() {
   432  			fmt.Printf("For this to work, you will need rclone available on a machine that has a web browser available.\n")
   433  			fmt.Printf("Execute the following on your machine (same rclone version recommended) :\n")
   434  			if changed {
   435  				fmt.Printf("\trclone authorize %q %q %q\n", id, oauthConfig.ClientID, oauthConfig.ClientSecret)
   436  			} else {
   437  				fmt.Printf("\trclone authorize %q\n", id)
   438  			}
   439  			fmt.Println("Then paste the result below:")
   440  			code := config.ReadNonEmptyLine("result> ")
   441  			token := &oauth2.Token{}
   442  			err := json.Unmarshal([]byte(code), token)
   443  			if err != nil {
   444  				return err
   445  			}
   446  			return PutToken(name, m, token, true)
   447  		}
   448  	}
   449  
   450  	// Make random state
   451  	state, err := random.Password(128)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	// Generate oauth URL
   457  	if offline {
   458  		opts = append(opts, oauth2.AccessTypeOffline)
   459  	}
   460  	authURL := oauthConfig.AuthCodeURL(state, opts...)
   461  
   462  	// Prepare webserver if needed
   463  	var server *authServer
   464  	if useWebServer {
   465  		server = newAuthServer(bindAddress, state, authURL)
   466  		err := server.Init()
   467  		if err != nil {
   468  			return errors.Wrap(err, "failed to start auth webserver")
   469  		}
   470  		go server.Serve()
   471  		defer server.Stop()
   472  		authURL = "http://" + bindAddress + "/auth?state=" + state
   473  	}
   474  
   475  	if !authorizeNoAutoBrowser && oauthConfig.RedirectURL != TitleBarRedirectURL {
   476  		// Open the URL for the user to visit
   477  		_ = open.Start(authURL)
   478  		fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL)
   479  	} else {
   480  		fmt.Printf("Please go to the following link: %s\n", authURL)
   481  	}
   482  	fmt.Printf("Log in and authorize rclone for access\n")
   483  
   484  	// Read the code via the webserver or manually
   485  	var auth *AuthResult
   486  	if useWebServer {
   487  		fmt.Printf("Waiting for code...\n")
   488  		auth = <-server.result
   489  		if !auth.OK || auth.Code == "" {
   490  			return auth
   491  		}
   492  		fmt.Printf("Got code\n")
   493  		if checkAuth != nil {
   494  			err = checkAuth(oauthConfig, auth)
   495  			if err != nil {
   496  				return err
   497  			}
   498  		}
   499  	} else {
   500  		auth = &AuthResult{
   501  			Code: config.ReadNonEmptyLine("Enter verification code> "),
   502  		}
   503  	}
   504  
   505  	// Exchange the code for a token
   506  	token, err := oauthConfig.Exchange(oauth2.NoContext, auth.Code)
   507  	if err != nil {
   508  		return errors.Wrap(err, "failed to get token")
   509  	}
   510  
   511  	// Print code if we are doing a manual auth
   512  	if authorizeOnly {
   513  		result, err := json.Marshal(token)
   514  		if err != nil {
   515  			return errors.Wrap(err, "failed to marshal token")
   516  		}
   517  		fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste\n", result)
   518  	}
   519  	return PutToken(name, m, token, true)
   520  }
   521  
   522  // Local web server for collecting auth
   523  type authServer struct {
   524  	state       string
   525  	listener    net.Listener
   526  	bindAddress string
   527  	authURL     string
   528  	server      *http.Server
   529  	result      chan *AuthResult
   530  }
   531  
   532  // newAuthServer makes the webserver for collecting auth
   533  func newAuthServer(bindAddress, state, authURL string) *authServer {
   534  	return &authServer{
   535  		state:       state,
   536  		bindAddress: bindAddress,
   537  		authURL:     authURL, // http://host/auth redirects to here
   538  		result:      make(chan *AuthResult, 1),
   539  	}
   540  }
   541  
   542  // Receive the auth request
   543  func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
   544  	fs.Debugf(nil, "Received %s request on auth server to %q", req.Method, req.URL.Path)
   545  
   546  	// Reply with the response to the user and to the channel
   547  	reply := func(status int, res *AuthResult) {
   548  		w.WriteHeader(status)
   549  		w.Header().Set("Content-Type", "text/html")
   550  		var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate))
   551  		if err := t.Execute(w, res); err != nil {
   552  			fs.Debugf(nil, "Could not execute template for web response.")
   553  		}
   554  		s.result <- res
   555  	}
   556  
   557  	// Parse the form parameters and save them
   558  	err := req.ParseForm()
   559  	if err != nil {
   560  		reply(http.StatusBadRequest, &AuthResult{
   561  			Name:        "Parse form error",
   562  			Description: err.Error(),
   563  		})
   564  		return
   565  	}
   566  
   567  	// get code, error if empty
   568  	code := req.Form.Get("code")
   569  	if code == "" {
   570  		reply(http.StatusBadRequest, &AuthResult{
   571  			Name:        "Auth Error",
   572  			Description: "No code returned by remote server",
   573  		})
   574  		return
   575  	}
   576  
   577  	// check state
   578  	state := req.Form.Get("state")
   579  	if state != s.state {
   580  		reply(http.StatusBadRequest, &AuthResult{
   581  			Name:        "Auth state doesn't match",
   582  			Description: fmt.Sprintf("Expecting %q got %q", s.state, state),
   583  		})
   584  		return
   585  	}
   586  
   587  	// code OK
   588  	reply(http.StatusOK, &AuthResult{
   589  		OK:   true,
   590  		Code: code,
   591  		Form: req.Form,
   592  	})
   593  }
   594  
   595  // Init gets the internal web server ready to receive config details
   596  func (s *authServer) Init() error {
   597  	fs.Debugf(nil, "Starting auth server on %s", s.bindAddress)
   598  	mux := http.NewServeMux()
   599  	s.server = &http.Server{
   600  		Addr:    s.bindAddress,
   601  		Handler: mux,
   602  	}
   603  	s.server.SetKeepAlivesEnabled(false)
   604  
   605  	mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
   606  		http.Error(w, "", http.StatusNotFound)
   607  		return
   608  	})
   609  	mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
   610  		state := req.FormValue("state")
   611  		if state != s.state {
   612  			fs.Debugf(nil, "State did not match: want %q got %q", s.state, state)
   613  			http.Error(w, "State did not match - please try again", http.StatusForbidden)
   614  			return
   615  		}
   616  		http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect)
   617  		return
   618  	})
   619  	mux.HandleFunc("/", s.handleAuth)
   620  
   621  	var err error
   622  	s.listener, err = net.Listen("tcp", s.bindAddress)
   623  	if err != nil {
   624  		return err
   625  	}
   626  	return nil
   627  }
   628  
   629  // Serve the auth server, doesn't return
   630  func (s *authServer) Serve() {
   631  	err := s.server.Serve(s.listener)
   632  	fs.Debugf(nil, "Closed auth server with error: %v", err)
   633  }
   634  
   635  // Stop the auth server by closing its socket
   636  func (s *authServer) Stop() {
   637  	fs.Debugf(nil, "Closing auth server")
   638  	close(s.result)
   639  	_ = s.listener.Close()
   640  
   641  	// close the server
   642  	_ = s.server.Close()
   643  }