forked from TrueCloudLab/rclone
lib/oauthutil: refactor web server and allow an auth callback
This commit is contained in:
parent
25a0e7e8aa
commit
f29e5b6e7d
1 changed files with 153 additions and 139 deletions
|
@ -2,13 +2,12 @@ package oauthutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,6 +16,7 @@ import (
|
||||||
"github.com/rclone/rclone/fs/config"
|
"github.com/rclone/rclone/fs/config"
|
||||||
"github.com/rclone/rclone/fs/config/configmap"
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
"github.com/rclone/rclone/fs/fshttp"
|
"github.com/rclone/rclone/fs/fshttp"
|
||||||
|
"github.com/rclone/rclone/lib/random"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
@ -46,8 +46,8 @@ const (
|
||||||
// redirects to the local webserver
|
// redirects to the local webserver
|
||||||
RedirectPublicSecureURL = "https://oauth.rclone.org/"
|
RedirectPublicSecureURL = "https://oauth.rclone.org/"
|
||||||
|
|
||||||
// AuthResponse is a template to handle the redirect URL for oauth requests
|
// AuthResponseTemplate is a template to handle the redirect URL for oauth requests
|
||||||
AuthResponse = `<!DOCTYPE html>
|
AuthResponseTemplate = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
@ -58,17 +58,12 @@ const (
|
||||||
<hr>
|
<hr>
|
||||||
<pre style="width: 750px; white-space: pre-wrap;">
|
<pre style="width: 750px; white-space: pre-wrap;">
|
||||||
{{ if eq .OK false }}
|
{{ if eq .OK false }}
|
||||||
Error: {{ .AuthError.Name }}<br>
|
Error: {{ .Name }}<br>
|
||||||
{{ if .AuthError.Description }}Description: {{ .AuthError.Description }}<br>{{ end }}
|
{{ if .Description }}Description: {{ .Description }}<br>{{ end }}
|
||||||
{{ if .AuthError.Code }}Code: {{ .AuthError.Code }}<br>{{ end }}
|
{{ if .Code }}Code: {{ .Code }}<br>{{ end }}
|
||||||
{{ if .AuthError.HelpURL }}Look here for help: <a href="{{ .AuthError.HelpURL }}">{{ .AuthError.HelpURL }}</a><br>{{ end }}
|
{{ if .HelpURL }}Look here for help: <a href="{{ .HelpURL }}">{{ .HelpURL }}</a><br>{{ end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ if .Code }}
|
|
||||||
Please copy this code into rclone:
|
|
||||||
{{ .Code }}
|
|
||||||
{{ else }}
|
|
||||||
All done. Please go back to rclone.
|
All done. Please go back to rclone.
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</pre>
|
</pre>
|
||||||
</body>
|
</body>
|
||||||
|
@ -340,26 +335,54 @@ func NewClient(name string, m configmap.Mapper, oauthConfig *oauth2.Config) (*ht
|
||||||
return NewClientWithBaseClient(name, m, oauthConfig, fshttp.NewClient(fs.Config))
|
return NewClientWithBaseClient(name, m, oauthConfig, fshttp.NewClient(fs.Config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuthResult is returned from the web server after authorization
|
||||||
|
// success or failure
|
||||||
|
type AuthResult struct {
|
||||||
|
OK bool // Failure or Success?
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Code string
|
||||||
|
HelpURL string
|
||||||
|
Form url.Values // the complete contents of the form
|
||||||
|
Err error // any underlying error to report
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfies the error interface so AuthResult can be used as an error
|
||||||
|
func (ar *AuthResult) Error() string {
|
||||||
|
status := "Error"
|
||||||
|
if ar.OK {
|
||||||
|
status = "OK"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: %s\nCode: %q\nDescription: %s\nHelp: %s",
|
||||||
|
status, ar.Name, ar.Code, ar.Description, ar.HelpURL)
|
||||||
|
}
|
||||||
|
|
||||||
// Config does the initial creation of the token
|
// Config does the initial creation of the token
|
||||||
//
|
//
|
||||||
// It may run an internal webserver to receive the results
|
// It may run an internal webserver to receive the results
|
||||||
func Config(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
|
func Config(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
|
||||||
return doConfig(id, name, m, nil, config, true, opts)
|
return doConfig(id, name, m, config, true, nil, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAuthFn is called when a good Auth has been received
|
||||||
|
type CheckAuthFn func(*oauth2.Config, *AuthResult) error
|
||||||
|
|
||||||
|
// ConfigWithCallback does the initial creation of the token
|
||||||
|
//
|
||||||
|
// It may run an internal webserver to receive the results
|
||||||
|
//
|
||||||
|
// When the AuthResult is known the checkAuth function is called if set
|
||||||
|
func ConfigWithCallback(id, name string, m configmap.Mapper, config *oauth2.Config, checkAuth CheckAuthFn, opts ...oauth2.AuthCodeOption) error {
|
||||||
|
return doConfig(id, name, m, config, true, checkAuth, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigNoOffline does the same as Config but does not pass the
|
// ConfigNoOffline does the same as Config but does not pass the
|
||||||
// "access_type=offline" parameter.
|
// "access_type=offline" parameter.
|
||||||
func ConfigNoOffline(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
|
func ConfigNoOffline(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
|
||||||
return doConfig(id, name, m, nil, config, false, opts)
|
return doConfig(id, name, m, config, false, nil, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigErrorCheck does the same as Config, but allows the backend to pass a error handling function
|
func doConfig(id, name string, m configmap.Mapper, oauthConfig *oauth2.Config, offline bool, checkAuth CheckAuthFn, opts []oauth2.AuthCodeOption) error {
|
||||||
// This function gets called with the request made to rclone as a parameter if no code was found
|
|
||||||
func ConfigErrorCheck(id, name string, m configmap.Mapper, errorHandler func(*http.Request) AuthError, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
|
|
||||||
return doConfig(id, name, m, errorHandler, config, true, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Request) AuthError, oauthConfig *oauth2.Config, offline bool, opts []oauth2.AuthCodeOption) error {
|
|
||||||
oauthConfig, changed := overrideCredentials(name, m, oauthConfig)
|
oauthConfig, changed := overrideCredentials(name, m, oauthConfig)
|
||||||
authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize)
|
authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize)
|
||||||
authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize"
|
authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize"
|
||||||
|
@ -384,7 +407,18 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
|
||||||
// Detect whether we should use internal web server
|
// Detect whether we should use internal web server
|
||||||
useWebServer := false
|
useWebServer := false
|
||||||
switch oauthConfig.RedirectURL {
|
switch oauthConfig.RedirectURL {
|
||||||
case RedirectURL, RedirectPublicURL, RedirectLocalhostURL, RedirectPublicSecureURL:
|
case TitleBarRedirectURL:
|
||||||
|
useWebServer = authorizeOnly
|
||||||
|
if !authorizeOnly {
|
||||||
|
useWebServer = isLocal()
|
||||||
|
}
|
||||||
|
if useWebServer {
|
||||||
|
// copy the config and set to use the internal webserver
|
||||||
|
configCopy := *oauthConfig
|
||||||
|
oauthConfig = &configCopy
|
||||||
|
oauthConfig.RedirectURL = RedirectURL
|
||||||
|
}
|
||||||
|
default:
|
||||||
if changed {
|
if changed {
|
||||||
fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
|
fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
|
||||||
}
|
}
|
||||||
|
@ -401,11 +435,7 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
|
||||||
fmt.Printf("\trclone authorize %q\n", id)
|
fmt.Printf("\trclone authorize %q\n", id)
|
||||||
}
|
}
|
||||||
fmt.Println("Then paste the result below:")
|
fmt.Println("Then paste the result below:")
|
||||||
code := ""
|
code := config.ReadNonEmptyLine("result> ")
|
||||||
for code == "" {
|
|
||||||
fmt.Printf("result> ")
|
|
||||||
code = strings.TrimSpace(config.ReadLine())
|
|
||||||
}
|
|
||||||
token := &oauth2.Token{}
|
token := &oauth2.Token{}
|
||||||
err := json.Unmarshal([]byte(code), token)
|
err := json.Unmarshal([]byte(code), token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -413,41 +443,24 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
|
||||||
}
|
}
|
||||||
return PutToken(name, m, token, true)
|
return PutToken(name, m, token, true)
|
||||||
}
|
}
|
||||||
case TitleBarRedirectURL:
|
|
||||||
useWebServer = authorizeOnly
|
|
||||||
if !authorizeOnly {
|
|
||||||
useWebServer = isLocal()
|
|
||||||
}
|
|
||||||
if useWebServer {
|
|
||||||
// copy the config and set to use the internal webserver
|
|
||||||
configCopy := *oauthConfig
|
|
||||||
oauthConfig = &configCopy
|
|
||||||
oauthConfig.RedirectURL = RedirectURL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make random state
|
// Make random state
|
||||||
stateBytes := make([]byte, 16)
|
state, err := random.Password(128)
|
||||||
_, err := rand.Read(stateBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
state := fmt.Sprintf("%x", stateBytes)
|
|
||||||
|
// Generate oauth URL
|
||||||
if offline {
|
if offline {
|
||||||
opts = append(opts, oauth2.AccessTypeOffline)
|
opts = append(opts, oauth2.AccessTypeOffline)
|
||||||
}
|
}
|
||||||
authURL := oauthConfig.AuthCodeURL(state, opts...)
|
authURL := oauthConfig.AuthCodeURL(state, opts...)
|
||||||
|
|
||||||
// Prepare webserver
|
// Prepare webserver if needed
|
||||||
server := authServer{
|
var server *authServer
|
||||||
state: state,
|
|
||||||
bindAddress: bindAddress,
|
|
||||||
authURL: authURL,
|
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
|
||||||
if useWebServer {
|
if useWebServer {
|
||||||
server.code = make(chan string, 1)
|
server = newAuthServer(bindAddress, state, authURL)
|
||||||
server.err = make(chan error, 1)
|
|
||||||
err := server.Init()
|
err := server.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to start auth webserver")
|
return errors.Wrap(err, "failed to start auth webserver")
|
||||||
|
@ -457,36 +470,39 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
|
||||||
authURL = "http://" + bindAddress + "/auth?state=" + state
|
authURL = "http://" + bindAddress + "/auth?state=" + state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a URL for the user to visit for authorization.
|
// Open the URL for the user to visit
|
||||||
_ = open.Start(authURL)
|
_ = open.Start(authURL)
|
||||||
fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL)
|
fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL)
|
||||||
fmt.Printf("Log in and authorize rclone for access\n")
|
fmt.Printf("Log in and authorize rclone for access\n")
|
||||||
|
|
||||||
var authCode string
|
// Read the code via the webserver or manually
|
||||||
|
var auth *AuthResult
|
||||||
if useWebServer {
|
if useWebServer {
|
||||||
// Read the code, and exchange it for a token.
|
|
||||||
fmt.Printf("Waiting for code...\n")
|
fmt.Printf("Waiting for code...\n")
|
||||||
authCode = <-server.code
|
auth = <-server.result
|
||||||
authError := <-server.err
|
if !auth.OK || auth.Code == "" {
|
||||||
if authCode != "" {
|
return auth
|
||||||
fmt.Printf("Got code\n")
|
}
|
||||||
} else {
|
fmt.Printf("Got code\n")
|
||||||
if authError != nil {
|
if checkAuth != nil {
|
||||||
return authError
|
err = checkAuth(oauthConfig, auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return errors.New("failed to get code")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Read the code, and exchange it for a token.
|
auth = &AuthResult{
|
||||||
fmt.Printf("Enter verification code> ")
|
Code: config.ReadNonEmptyLine("Enter verification code> "),
|
||||||
authCode = config.ReadLine()
|
}
|
||||||
}
|
}
|
||||||
token, err := oauthConfig.Exchange(oauth2.NoContext, authCode)
|
|
||||||
|
// Exchange the code for a token
|
||||||
|
token, err := oauthConfig.Exchange(oauth2.NoContext, auth.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to get token")
|
return errors.Wrap(err, "failed to get token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print code if we do automatic retrieval
|
// Print code if we are doing a manual auth
|
||||||
if authorizeOnly {
|
if authorizeOnly {
|
||||||
result, err := json.Marshal(token)
|
result, err := json.Marshal(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -499,29 +515,75 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
|
||||||
|
|
||||||
// Local web server for collecting auth
|
// Local web server for collecting auth
|
||||||
type authServer struct {
|
type authServer struct {
|
||||||
state string
|
state string
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
bindAddress string
|
bindAddress string
|
||||||
code chan string
|
authURL string
|
||||||
err chan error
|
server *http.Server
|
||||||
authURL string
|
result chan *AuthResult
|
||||||
server *http.Server
|
|
||||||
errorHandler func(*http.Request) AuthError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthError gets returned by the backend's errorHandler function
|
// newAuthServer makes the webserver for collecting auth
|
||||||
type AuthError struct {
|
func newAuthServer(bindAddress, state, authURL string) *authServer {
|
||||||
Name string
|
return &authServer{
|
||||||
Description string
|
state: state,
|
||||||
Code string
|
bindAddress: bindAddress,
|
||||||
HelpURL string
|
authURL: authURL, // http://host/auth redirects to here
|
||||||
|
result: make(chan *AuthResult, 1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResponseData can fill the AuthResponse template
|
// Receive the auth request
|
||||||
type AuthResponseData struct {
|
func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
|
||||||
OK bool // Failure or Success?
|
fs.Debugf(nil, "Received %s request on auth server to %q", req.Method, req.URL.Path)
|
||||||
Code string // code to paste into rclone config
|
|
||||||
AuthError
|
// Reply with the response to the user and to the channel
|
||||||
|
reply := func(status int, res *AuthResult) {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate))
|
||||||
|
if err := t.Execute(w, res); err != nil {
|
||||||
|
fs.Debugf(nil, "Could not execute template for web response.")
|
||||||
|
}
|
||||||
|
s.result <- res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the form parameters and save them
|
||||||
|
err := req.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
reply(http.StatusBadRequest, &AuthResult{
|
||||||
|
Name: "Parse form error",
|
||||||
|
Description: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get code, error if empty
|
||||||
|
code := req.Form.Get("code")
|
||||||
|
if code == "" {
|
||||||
|
reply(http.StatusBadRequest, &AuthResult{
|
||||||
|
Name: "Auth Error",
|
||||||
|
Description: "No code returned by remote server",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check state
|
||||||
|
state := req.Form.Get("state")
|
||||||
|
if state != s.state {
|
||||||
|
reply(http.StatusBadRequest, &AuthResult{
|
||||||
|
Name: "Auth state doesn't match",
|
||||||
|
Description: fmt.Sprintf("Expecting %q got %q", s.state, state),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// code OK
|
||||||
|
reply(http.StatusOK, &AuthResult{
|
||||||
|
OK: true,
|
||||||
|
Code: code,
|
||||||
|
Form: req.Form,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init gets the internal web server ready to receive config details
|
// Init gets the internal web server ready to receive config details
|
||||||
|
@ -533,67 +595,22 @@ func (s *authServer) Init() error {
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
s.server.SetKeepAlivesEnabled(false)
|
s.server.SetKeepAlivesEnabled(false)
|
||||||
|
|
||||||
mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
|
||||||
http.Error(w, "", 404)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
|
||||||
state := req.FormValue("state")
|
state := req.FormValue("state")
|
||||||
if state != s.state {
|
if state != s.state {
|
||||||
fs.Debugf(nil, "State did not match: want %q got %q", s.state, state)
|
fs.Debugf(nil, "State did not match: want %q got %q", s.state, state)
|
||||||
http.Error(w, "State did not match - please try again", 403)
|
http.Error(w, "State did not match - please try again", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect)
|
http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/", s.handleAuth)
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
fs.Debugf(nil, "Received request on auth server")
|
|
||||||
code := req.FormValue("code")
|
|
||||||
var err error
|
|
||||||
var t = template.Must(template.New("authResponse").Parse(AuthResponse))
|
|
||||||
resp := AuthResponseData{AuthError: AuthError{}}
|
|
||||||
if code != "" {
|
|
||||||
state := req.FormValue("state")
|
|
||||||
if state != s.state {
|
|
||||||
fs.Debugf(nil, "State did not match: want %q got %q", s.state, state)
|
|
||||||
resp.OK = false
|
|
||||||
resp.AuthError = AuthError{
|
|
||||||
Name: "Auth State doesn't match",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs.Debugf(nil, "Successfully got code")
|
|
||||||
resp.OK = true
|
|
||||||
if s.code == nil {
|
|
||||||
resp.Code = code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs.Debugf(nil, "No code found on request")
|
|
||||||
var authError AuthError
|
|
||||||
if s.errorHandler == nil {
|
|
||||||
authError = AuthError{
|
|
||||||
Name: "Auth Error",
|
|
||||||
Description: "No code found returned by remote server.",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
authError = s.errorHandler(req)
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("Error: %s\nCode: %s\nDescription: %s\nHelp: %s",
|
|
||||||
authError.Name, authError.Code, authError.Description, authError.HelpURL)
|
|
||||||
resp.OK = false
|
|
||||||
resp.AuthError = authError
|
|
||||||
w.WriteHeader(500)
|
|
||||||
}
|
|
||||||
if err := t.Execute(w, resp); err != nil {
|
|
||||||
fs.Debugf(nil, "Could not execute template for web response.")
|
|
||||||
}
|
|
||||||
if s.code != nil {
|
|
||||||
s.code <- code
|
|
||||||
s.err <- err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s.listener, err = net.Listen("tcp", s.bindAddress)
|
s.listener, err = net.Listen("tcp", s.bindAddress)
|
||||||
|
@ -612,10 +629,7 @@ func (s *authServer) Serve() {
|
||||||
// Stop the auth server by closing its socket
|
// Stop the auth server by closing its socket
|
||||||
func (s *authServer) Stop() {
|
func (s *authServer) Stop() {
|
||||||
fs.Debugf(nil, "Closing auth server")
|
fs.Debugf(nil, "Closing auth server")
|
||||||
if s.code != nil {
|
close(s.result)
|
||||||
close(s.code)
|
|
||||||
s.code = nil
|
|
||||||
}
|
|
||||||
_ = s.listener.Close()
|
_ = s.listener.Close()
|
||||||
|
|
||||||
// close the server
|
// close the server
|
||||||
|
|
Loading…
Reference in a new issue