github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/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.Errorf("empty token found - please run \"rclone config reconnect %s:\"", name)
    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, errors.Wrapf(err, "couldn't fetch token - maybe it has expired? - refresh with \"rclone config reconnect %s:\"", ts.name)
   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, errors.Wrap(err, "couldn't store token")
   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  // CheckAuthFn is called when a good Auth has been received
   361  type CheckAuthFn func(*oauth2.Config, *AuthResult) error
   362  
   363  // Options for the oauth config
   364  type Options struct {
   365  	NoOffline    bool                    // If set then "access_type=offline" parameter is not passed
   366  	CheckAuth    CheckAuthFn             // When the AuthResult is known the checkAuth function is called if set
   367  	OAuth2Opts   []oauth2.AuthCodeOption // extra oauth2 options
   368  	StateBlankOK bool                    // If set, state returned as "" is deemed to be OK
   369  }
   370  
   371  // Config does the initial creation of the token
   372  //
   373  // If opt is nil it will use the default Options
   374  //
   375  // It may run an internal webserver to receive the results
   376  func Config(id, name string, m configmap.Mapper, oauthConfig *oauth2.Config, opt *Options) error {
   377  	if opt == nil {
   378  		opt = &Options{}
   379  	}
   380  	oauthConfig, changed := overrideCredentials(name, m, oauthConfig)
   381  	authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize)
   382  	authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize"
   383  	authorizeNoAutoBrowserValue, ok := m.Get(config.ConfigAuthNoBrowser)
   384  	authorizeNoAutoBrowser := ok && authorizeNoAutoBrowserValue != ""
   385  
   386  	// See if already have a token
   387  	tokenString, ok := m.Get("token")
   388  	if ok && tokenString != "" {
   389  		fmt.Printf("Already have a token - refresh?\n")
   390  		if !config.ConfirmWithConfig(m, "config_refresh_token", true) {
   391  			return nil
   392  		}
   393  	}
   394  
   395  	// Ask the user whether they are using a local machine
   396  	isLocal := func() bool {
   397  		fmt.Printf("Use auto config?\n")
   398  		fmt.Printf(" * Say Y if not sure\n")
   399  		fmt.Printf(" * Say N if you are working on a remote or headless machine\n")
   400  		return config.ConfirmWithConfig(m, "config_is_local", true)
   401  	}
   402  
   403  	// Detect whether we should use internal web server
   404  	useWebServer := false
   405  	switch oauthConfig.RedirectURL {
   406  	case TitleBarRedirectURL:
   407  		useWebServer = authorizeOnly
   408  		if !authorizeOnly {
   409  			useWebServer = isLocal()
   410  		}
   411  		if useWebServer {
   412  			// copy the config and set to use the internal webserver
   413  			configCopy := *oauthConfig
   414  			oauthConfig = &configCopy
   415  			oauthConfig.RedirectURL = RedirectURL
   416  		}
   417  	default:
   418  		if changed {
   419  			fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
   420  		}
   421  		useWebServer = true
   422  		if authorizeOnly {
   423  			break
   424  		}
   425  		if !isLocal() {
   426  			fmt.Printf(`For this to work, you will need rclone available on a machine that has
   427  a web browser available.
   428  
   429  For more help and alternate methods see: https://rclone.org/remote_setup/
   430  
   431  Execute the following on the machine with the web browser (same rclone
   432  version recommended):
   433  
   434  `)
   435  			if changed {
   436  				fmt.Printf("\trclone authorize %q -- %q %q\n", id, oauthConfig.ClientID, oauthConfig.ClientSecret)
   437  			} else {
   438  				fmt.Printf("\trclone authorize %q\n", id)
   439  			}
   440  			fmt.Println("\nThen paste the result below:")
   441  			code := config.ReadNonEmptyLine("result> ")
   442  			token := &oauth2.Token{}
   443  			err := json.Unmarshal([]byte(code), token)
   444  			if err != nil {
   445  				return err
   446  			}
   447  			return PutToken(name, m, token, true)
   448  		}
   449  	}
   450  
   451  	// Make random state
   452  	state, err := random.Password(128)
   453  	if err != nil {
   454  		return err
   455  	}
   456  
   457  	// Generate oauth URL
   458  	opts := opt.OAuth2Opts
   459  	if !opt.NoOffline {
   460  		opts = append(opts, oauth2.AccessTypeOffline)
   461  	}
   462  	authURL := oauthConfig.AuthCodeURL(state, opts...)
   463  
   464  	// Prepare webserver if needed
   465  	var server *authServer
   466  	if useWebServer {
   467  		server = newAuthServer(opt, bindAddress, state, authURL)
   468  		err := server.Init()
   469  		if err != nil {
   470  			return errors.Wrap(err, "failed to start auth webserver")
   471  		}
   472  		go server.Serve()
   473  		defer server.Stop()
   474  		authURL = "http://" + bindAddress + "/auth?state=" + state
   475  	}
   476  
   477  	if !authorizeNoAutoBrowser && oauthConfig.RedirectURL != TitleBarRedirectURL {
   478  		// Open the URL for the user to visit
   479  		_ = open.Start(authURL)
   480  		fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL)
   481  	} else {
   482  		fmt.Printf("Please go to the following link: %s\n", authURL)
   483  	}
   484  	fmt.Printf("Log in and authorize rclone for access\n")
   485  
   486  	// Read the code via the webserver or manually
   487  	var auth *AuthResult
   488  	if useWebServer {
   489  		fmt.Printf("Waiting for code...\n")
   490  		auth = <-server.result
   491  		if !auth.OK || auth.Code == "" {
   492  			return auth
   493  		}
   494  		fmt.Printf("Got code\n")
   495  		if opt.CheckAuth != nil {
   496  			err = opt.CheckAuth(oauthConfig, auth)
   497  			if err != nil {
   498  				return err
   499  			}
   500  		}
   501  	} else {
   502  		auth = &AuthResult{
   503  			Code: config.ReadNonEmptyLine("Enter verification code> "),
   504  		}
   505  	}
   506  
   507  	// Exchange the code for a token
   508  	ctx := Context(fshttp.NewClient(fs.Config))
   509  	token, err := oauthConfig.Exchange(ctx, auth.Code)
   510  	if err != nil {
   511  		return errors.Wrap(err, "failed to get token")
   512  	}
   513  
   514  	// Print code if we are doing a manual auth
   515  	if authorizeOnly {
   516  		result, err := json.Marshal(token)
   517  		if err != nil {
   518  			return errors.Wrap(err, "failed to marshal token")
   519  		}
   520  		fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste\n", result)
   521  	}
   522  	return PutToken(name, m, token, true)
   523  }
   524  
   525  // Local web server for collecting auth
   526  type authServer struct {
   527  	opt         *Options
   528  	state       string
   529  	listener    net.Listener
   530  	bindAddress string
   531  	authURL     string
   532  	server      *http.Server
   533  	result      chan *AuthResult
   534  }
   535  
   536  // newAuthServer makes the webserver for collecting auth
   537  func newAuthServer(opt *Options, bindAddress, state, authURL string) *authServer {
   538  	return &authServer{
   539  		opt:         opt,
   540  		state:       state,
   541  		bindAddress: bindAddress,
   542  		authURL:     authURL, // http://host/auth redirects to here
   543  		result:      make(chan *AuthResult, 1),
   544  	}
   545  }
   546  
   547  // Receive the auth request
   548  func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
   549  	fs.Debugf(nil, "Received %s request on auth server to %q", req.Method, req.URL.Path)
   550  
   551  	// Reply with the response to the user and to the channel
   552  	reply := func(status int, res *AuthResult) {
   553  		w.WriteHeader(status)
   554  		w.Header().Set("Content-Type", "text/html")
   555  		var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate))
   556  		if err := t.Execute(w, res); err != nil {
   557  			fs.Debugf(nil, "Could not execute template for web response.")
   558  		}
   559  		s.result <- res
   560  	}
   561  
   562  	// Parse the form parameters and save them
   563  	err := req.ParseForm()
   564  	if err != nil {
   565  		reply(http.StatusBadRequest, &AuthResult{
   566  			Name:        "Parse form error",
   567  			Description: err.Error(),
   568  		})
   569  		return
   570  	}
   571  
   572  	// get code, error if empty
   573  	code := req.Form.Get("code")
   574  	if code == "" {
   575  		reply(http.StatusBadRequest, &AuthResult{
   576  			Name:        "Auth Error",
   577  			Description: "No code returned by remote server",
   578  		})
   579  		return
   580  	}
   581  
   582  	// check state
   583  	state := req.Form.Get("state")
   584  	if state != s.state && !(state == "" && s.opt.StateBlankOK) {
   585  		reply(http.StatusBadRequest, &AuthResult{
   586  			Name:        "Auth state doesn't match",
   587  			Description: fmt.Sprintf("Expecting %q got %q", s.state, state),
   588  		})
   589  		return
   590  	}
   591  
   592  	// code OK
   593  	reply(http.StatusOK, &AuthResult{
   594  		OK:   true,
   595  		Code: code,
   596  		Form: req.Form,
   597  	})
   598  }
   599  
   600  // Init gets the internal web server ready to receive config details
   601  func (s *authServer) Init() error {
   602  	fs.Debugf(nil, "Starting auth server on %s", s.bindAddress)
   603  	mux := http.NewServeMux()
   604  	s.server = &http.Server{
   605  		Addr:    s.bindAddress,
   606  		Handler: mux,
   607  	}
   608  	s.server.SetKeepAlivesEnabled(false)
   609  
   610  	mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
   611  		http.Error(w, "", http.StatusNotFound)
   612  		return
   613  	})
   614  	mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
   615  		state := req.FormValue("state")
   616  		if state != s.state {
   617  			fs.Debugf(nil, "State did not match: want %q got %q", s.state, state)
   618  			http.Error(w, "State did not match - please try again", http.StatusForbidden)
   619  			return
   620  		}
   621  		http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect)
   622  		return
   623  	})
   624  	mux.HandleFunc("/", s.handleAuth)
   625  
   626  	var err error
   627  	s.listener, err = net.Listen("tcp", s.bindAddress)
   628  	if err != nil {
   629  		return err
   630  	}
   631  	return nil
   632  }
   633  
   634  // Serve the auth server, doesn't return
   635  func (s *authServer) Serve() {
   636  	err := s.server.Serve(s.listener)
   637  	fs.Debugf(nil, "Closed auth server with error: %v", err)
   638  }
   639  
   640  // Stop the auth server by closing its socket
   641  func (s *authServer) Stop() {
   642  	fs.Debugf(nil, "Closing auth server")
   643  	close(s.result)
   644  	_ = s.listener.Close()
   645  
   646  	// close the server
   647  	_ = s.server.Close()
   648  }