StartCallbackServer starts a local HTTP server on a random available port. It returns the redirect URI (http://127.0.0.1:{port}) and a channel that will receive exactly one CallbackResult when the OAuth redirect arrives. The server shuts itself down after handling the first request.
()
| 74 | // will receive exactly one CallbackResult when the OAuth redirect arrives. |
| 75 | // The server shuts itself down after handling the first request. |
| 76 | func StartCallbackServer() (redirectURI string, result <-chan CallbackResult, err error) { |
| 77 | listener, err := net.Listen("tcp", "127.0.0.1:0") |
| 78 | if err != nil { |
| 79 | return "", nil, fmt.Errorf("failed to start callback server: %w", err) |
| 80 | } |
| 81 | |
| 82 | port := listener.Addr().(*net.TCPAddr).Port |
| 83 | redirectURI = fmt.Sprintf("http://127.0.0.1:%d", port) |
| 84 | |
| 85 | ch := make(chan CallbackResult, 1) |
| 86 | |
| 87 | mux := http.NewServeMux() |
| 88 | srv := &http.Server{Handler: mux, ReadHeaderTimeout: 10 * time.Second} |
| 89 | |
| 90 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
| 91 | q := r.URL.Query() |
| 92 | |
| 93 | if errParam := q.Get("error"); errParam != "" { |
| 94 | desc := q.Get("error_description") |
| 95 | if desc == "" { |
| 96 | desc = errParam |
| 97 | } |
| 98 | http.Error(w, desc, http.StatusBadRequest) |
| 99 | ch <- CallbackResult{Error: desc} |
| 100 | } else { |
| 101 | w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| 102 | fmt.Fprint(w, successHTML) |
| 103 | ch <- CallbackResult{Code: q.Get("code")} |
| 104 | } |
| 105 | |
| 106 | go func() { |
| 107 | ctx, cancel := context.WithTimeout(context.Background(), callbackShutdownTimeout) |
| 108 | defer cancel() |
| 109 | _ = srv.Shutdown(ctx) |
| 110 | }() |
| 111 | }) |
| 112 | |
| 113 | go func() { _ = srv.Serve(listener) }() |
| 114 | |
| 115 | return redirectURI, ch, nil |
| 116 | } |