/
handler.go
107 lines (90 loc) · 2.65 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package auth
import (
"context"
"embed"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/charmbracelet/log"
"golang.org/x/oauth2"
)
//go:embed assets
var assets embed.FS
type OAuthHandler struct {
listener net.Listener
config oauth2.Config
token *oauth2.Token
}
func NewOAuthHandler(config oauth2.Config, bindPort uint) (*OAuthHandler, error) {
address := net.JoinHostPort("localhost", strconv.Itoa(int(bindPort)))
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return nil, err
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, err
}
return &OAuthHandler{
config: config,
listener: listener,
}, nil
}
func (l *OAuthHandler) Configuration() *oauth2.Config {
redirectURL := url.URL{
Scheme: "http",
Host: net.JoinHostPort("localhost", strconv.Itoa(l.listener.Addr().(*net.TCPAddr).Port)),
Path: "/redirect",
}
return &oauth2.Config{
ClientID: l.config.ClientID,
ClientSecret: l.config.ClientSecret,
Endpoint: l.config.Endpoint,
RedirectURL: redirectURL.String(),
Scopes: l.config.Scopes,
}
}
func (l *OAuthHandler) Token() *oauth2.Token {
return l.token
}
func (l *OAuthHandler) createAuthorizationExchange(ctx context.Context) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
defer l.listener.Close()
authorizationCode := req.URL.Query().Get("code")
var err error
// exchange authorization token for access and refresh token
l.token, err = l.Configuration().Exchange(context.WithValue(ctx, oauth2.HTTPClient, http.DefaultClient), authorizationCode)
if err != nil {
log.Error(err, "method", "createAuthorizationExchange")
w.WriteHeader(http.StatusInternalServerError)
return
}
// show the user a success page and stop the http listener
w.WriteHeader(http.StatusOK)
successPage, err := assets.ReadFile("assets/index.html")
if err != nil {
log.Fatal("could not load auth success page", err)
}
_, err = w.Write(successPage)
if err != nil {
log.Fatal(err)
}
}
}
// Listen is meant to be called as goroutine. Once your handler has finished, just cancel the context
// and the http server will shut down.
func (l *OAuthHandler) Listen(ctx context.Context) error {
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.FS(assets)))
mux.HandleFunc("/redirect", l.createAuthorizationExchange(ctx))
if err := http.Serve(l.listener, mux); err != nil {
// Chrome sometimes requests the favicon after the oauth request and we do not want to panic here.
if strings.Contains(err.Error(), "use of closed network connection") {
return nil
}
return err
}
return nil
}