diff options
| author | mo khan <mo@mokhan.ca> | 2022-05-16 10:00:19 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2022-05-16 10:00:19 -0600 |
| commit | c664deedf96f5ec088ab221a21a1eb26c3ca12ed (patch) | |
| tree | 4d7bd74835296241595f6c4d56d9d2e044c08303 | |
| parent | 835cf264667053e2ca9b67f56fa85ba38a89b6f0 (diff) | |
record request/response
| -rw-r--r-- | cmd/ui/main.go | 193 | ||||
| -rw-r--r-- | pkg/x/must.go | 10 | ||||
| -rw-r--r-- | pkg/x/session.go | 35 |
3 files changed, 133 insertions, 105 deletions
diff --git a/cmd/ui/main.go b/cmd/ui/main.go index 84b733d..6bb3ec8 100644 --- a/cmd/ui/main.go +++ b/cmd/ui/main.go @@ -1,188 +1,171 @@ package main import ( + "bytes" "context" - "errors" "fmt" "html/template" + "io/ioutil" "log" "net/http" "net/url" "os" + "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/hashicorp/uuid" "github.com/joho/godotenv" + "github.com/xlgmokha/api-auth0/pkg/x" "golang.org/x/oauth2" ) -type Session struct { - Token *oauth2.Token - IdToken *oidc.IDToken - IdTokenRaw interface{} - Profile map[string]interface{} - Message string - OAuthState string -} - -func (s *Session) IsLoggedIn() bool { - return s.Token != nil -} - -func (s *Session) Flash(msg string, w http.ResponseWriter, r *http.Request) { - s.Message = msg - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) -} - -func (s *Session) Reset() { - s.Token = nil - s.IdToken = nil - s.IdTokenRaw = nil - s.Profile = map[string]interface{}{} - s.Message = "" - s.OAuthState = "" -} +const ( + SessionCookie string = "c0_session" +) -func sessionFor(sessions map[string]*Session, r *http.Request, w http.ResponseWriter) *Session { - cookie, err := r.Cookie("session") +func SessionFor(sessions map[string]*x.Session, r *http.Request, w http.ResponseWriter) *x.Session { + cookie, err := r.Cookie(SessionCookie) var sessionId string if err != nil { sessionId = uuid.GenerateUUID() } else { sessionId = cookie.Value } - http.SetCookie(w, &http.Cookie{Name: "session", Value: sessionId}) + http.SetCookie(w, &http.Cookie{Name: SessionCookie, Value: sessionId}) session, ok := sessions[sessionId] if !ok { - session = &Session{} + session = &x.Session{} sessions[sessionId] = session } return session } -type Authenticator struct { - *oidc.Provider - oauth2.Config +type LoggingRoundTripper struct { + Proxied http.RoundTripper } -func Must[T any](x T, err error) T { - if err != nil { - log.Fatal(err) - } - return x -} +func (l LoggingRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + body := x.Must(ioutil.ReadAll(r.Body)) + r.Body = ioutil.NopCloser(bytes.NewBuffer(body)) -func NewAuthenticator() (*Authenticator, error) { - provider := Must(oidc.NewProvider(context.Background(), "https://"+os.Getenv("AUTH0_DOMAIN")+"/")) - - return &Authenticator{ - Provider: provider, - Config: oauth2.Config{ - ClientID: os.Getenv("AUTH0_CLIENT_ID"), - ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), - RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), - Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile"}, - }, - }, nil -} + fmt.Println(strings.Repeat("-", 80)) + fmt.Printf("%v %v\n", r.Method, r.URL) + for key, values := range r.Header { + if len(values) == 1 { + fmt.Printf("%v: %v\n", key, values[0]) + } else { + fmt.Printf("%v: %v\n", key, values) + } + } + fmt.Printf("\n") + fmt.Printf("%v\n", string(body)) -// VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken. -func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) { - rawIDToken, ok := token.Extra("id_token").(string) - if !ok { - return nil, errors.New("no id_token field in oauth2 token") + params := x.Must(url.ParseQuery(string(body))) + for key, values := range params { + if len(values) == 1 { + fmt.Printf("\t%v: %v\n", key, values[0]) + } else { + fmt.Printf("\t%v: %v\n", key, values) + } } + fmt.Println(strings.Repeat("-", 80)) + + response, err := l.Proxied.RoundTrip(r) - oidcConfig := &oidc.Config{ - ClientID: a.ClientID, + fmt.Printf("%v %v\n", response.StatusCode, http.StatusText(response.StatusCode)) + for key, values := range response.Header { + if len(values) == 1 { + fmt.Printf("%v: %v\n", key, values[0]) + } else { + fmt.Printf("%v: %v\n", key, values) + } } + fmt.Printf("\n") + responseBody := x.Must(ioutil.ReadAll(response.Body)) + response.Body = ioutil.NopCloser(bytes.NewBuffer(responseBody)) + fmt.Printf("%v\n", string(responseBody)) - return a.Verifier(oidcConfig).Verify(ctx, rawIDToken) + return response, err } func main() { - sessions := map[string]*Session{} + sessions := map[string]*x.Session{} godotenv.Load() - auth, _ := NewAuthenticator() + provider := x.Must(oidc.NewProvider(context.Background(), "https://"+os.Getenv("AUTH0_DOMAIN")+"/")) + cfg := oauth2.Config{ + ClientID: os.Getenv("AUTH0_CLIENT_ID"), + ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), + RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), + Endpoint: provider.Endpoint(), + Scopes: []string{ + oidc.ScopeOpenID, + oidc.ScopeOfflineAccess, + "profile", + }, + } router := http.NewServeMux() router.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session := sessionFor(sessions, r, w) + session := SessionFor(sessions, r, w) - tmpl := template.Must(template.New("index.html").Delims("<%=", "%>").ParseFiles("cmd/ui/index.html")) - if err := tmpl.Execute(w, session); err != nil { - log.Fatal(err) - } else { - session.Message = "" - } + tmpl := x.Must(template.New("index.html").Delims("<%=", "%>").ParseFiles("cmd/ui/index.html")) + tmpl.Execute(w, session) + session.Message = "" })) router.Handle("/login", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session := sessionFor(sessions, r, w) + session := SessionFor(sessions, r, w) session.OAuthState = uuid.GenerateUUID() - fmt.Printf("Sessions: %v\n", sessions) - - http.Redirect(w, r, auth.AuthCodeURL(session.OAuthState), http.StatusTemporaryRedirect) + http.Redirect(w, r, cfg.AuthCodeURL(session.OAuthState), http.StatusTemporaryRedirect) })) router.Handle("/callback", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session := sessionFor(sessions, r, w) + session := SessionFor(sessions, r, w) state := r.URL.Query().Get("state") if state != session.OAuthState { session.Flash("Invalid state parameter", w, r) return } + var profile map[string]interface{} code := r.URL.Query().Get("code") - token, err := auth.Exchange(r.Context(), code) - if err != nil { - session.Flash("Invalid authorization code", w, r) - return + client := &http.Client{ + Transport: LoggingRoundTripper{http.DefaultTransport}, } - idToken, err := auth.VerifyIDToken(r.Context(), token) - if err != nil { - session.Flash("Unable to verify id token", w, r) - return - } + ctx := context.WithValue(r.Context(), oauth2.HTTPClient, client) - var profile map[string]interface{} - if err := idToken.Claims(&profile); err != nil { - session.Flash("Unable to parse id token claims", w, r) - return + token := x.Must(cfg.Exchange(ctx, code, oauth2.SetAuthURLParam("audience", os.Getenv("AUTH0_AUDIENCE")))) + + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + log.Panic("no id_token field in oauth2 token") } + idToken := x.Must(provider. + Verifier(&oidc.Config{ClientID: cfg.ClientID}). + Verify(r.Context(), rawIDToken)) + + idToken.Claims(&profile) + session.Token = token session.IdToken = idToken session.IdTokenRaw = token.Extra("id_token") session.Profile = profile + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) })) router.Handle("/logout", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logoutURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout") - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - returnTo, err := url.Parse(scheme + "://" + r.Host) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } + logoutURL := x.Must(url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout")) + returnTo := x.Must(url.Parse("http://" + r.Host)) parameters := url.Values{} parameters.Add("returnTo", returnTo.String()) parameters.Add("client_id", os.Getenv("AUTH0_CLIENT_ID")) logoutURL.RawQuery = parameters.Encode() http.Redirect(w, r, logoutURL.String(), http.StatusTemporaryRedirect) - sessionFor(sessions, r, w).Reset() + SessionFor(sessions, r, w).Reset() })) fmt.Println("listening localhost:3010") diff --git a/pkg/x/must.go b/pkg/x/must.go new file mode 100644 index 0000000..4ba6fe2 --- /dev/null +++ b/pkg/x/must.go @@ -0,0 +1,10 @@ +package x + +import "log" + +func Must[T any](x T, err error) T { + if err != nil { + log.Fatal(err) + } + return x +} diff --git a/pkg/x/session.go b/pkg/x/session.go new file mode 100644 index 0000000..bf8374d --- /dev/null +++ b/pkg/x/session.go @@ -0,0 +1,35 @@ +package x + +import ( + "net/http" + + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +type Session struct { + Token *oauth2.Token + IdToken *oidc.IDToken + IdTokenRaw interface{} + Profile map[string]interface{} + Message string + OAuthState string +} + +func (s *Session) IsLoggedIn() bool { + return s.Token != nil +} + +func (s *Session) Flash(msg string, w http.ResponseWriter, r *http.Request) { + s.Message = msg + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) +} + +func (s *Session) Reset() { + s.Token = nil + s.IdToken = nil + s.IdTokenRaw = nil + s.Profile = map[string]interface{}{} + s.Message = "" + s.OAuthState = "" +} |
