summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2022-05-16 10:00:19 -0600
committermo khan <mo@mokhan.ca>2022-05-16 10:00:19 -0600
commitc664deedf96f5ec088ab221a21a1eb26c3ca12ed (patch)
tree4d7bd74835296241595f6c4d56d9d2e044c08303
parent835cf264667053e2ca9b67f56fa85ba38a89b6f0 (diff)
record request/response
-rw-r--r--cmd/ui/main.go193
-rw-r--r--pkg/x/must.go10
-rw-r--r--pkg/x/session.go35
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 = ""
+}