diff --git a/handlers.go b/handlers.go index af63a64..58876d1 100644 --- a/handlers.go +++ b/handlers.go @@ -22,7 +22,7 @@ func jsonHandler(res http.ResponseWriter, req *http.Request) { return } - claims := session.Values["claims"].(Claims) + claims := session.Values["id_token"].(Claims) if err = json.NewEncoder(res).Encode(claims); err != nil { writeErr(res, err, "error encoding claims", http.StatusInternalServerError) @@ -44,25 +44,31 @@ func indexHandler(res http.ResponseWriter, req *http.Request) { if logged, ok := session.Values["logged"].(bool); ok && logged { tpl.LoggedIn = true - tpl.Claims = session.Values["claims"].(Claims) + tpl.Claims.IDToken = session.Values["id_token"].(Claims) + tpl.Claims.UserInfo = session.Values["userinfo"].(Claims) if len(options.GroupsFilter) >= 1 { - for _, group := range tpl.Claims.Groups { + for _, group := range tpl.Claims.UserInfo.Groups { if isStringInSlice(group, options.GroupsFilter) { tpl.Groups = append(tpl.Groups, filterText(group, options.Filters)) } } } else { - tpl.Groups = filterSliceOfText(tpl.Claims.Groups, options.Filters) + tpl.Groups = filterSliceOfText(tpl.Claims.UserInfo.Groups, options.Filters) } - tpl.Claims.PreferredUsername = filterText(tpl.Claims.PreferredUsername, options.Filters) - tpl.Claims.Audience = filterSliceOfText(tpl.Claims.Audience, options.Filters) - tpl.Claims.Issuer = filterText(tpl.Claims.Issuer, options.Filters) - tpl.Claims.Email = filterText(tpl.Claims.Email, options.Filters) - tpl.Claims.Name = filterText(tpl.Claims.Name, options.Filters) - tpl.RawToken = rawTokens[tpl.Claims.JWTIdentifier] - tpl.AuthorizeCodeURL = acURLs[tpl.Claims.JWTIdentifier].String() + tpl.Claims.IDToken.PreferredUsername = filterText(tpl.Claims.IDToken.PreferredUsername, options.Filters) + tpl.Claims.UserInfo.PreferredUsername = filterText(tpl.Claims.UserInfo.PreferredUsername, options.Filters) + tpl.Claims.IDToken.Audience = filterSliceOfText(tpl.Claims.IDToken.Audience, options.Filters) + tpl.Claims.UserInfo.Audience = filterSliceOfText(tpl.Claims.UserInfo.Audience, options.Filters) + tpl.Claims.IDToken.Issuer = filterText(tpl.Claims.IDToken.Issuer, options.Filters) + tpl.Claims.UserInfo.Issuer = filterText(tpl.Claims.UserInfo.Issuer, options.Filters) + tpl.Claims.IDToken.Email = filterText(tpl.Claims.IDToken.Email, options.Filters) + tpl.Claims.UserInfo.Email = filterText(tpl.Claims.UserInfo.Email, options.Filters) + tpl.Claims.IDToken.Name = filterText(tpl.Claims.IDToken.Name, options.Filters) + tpl.Claims.UserInfo.Name = filterText(tpl.Claims.UserInfo.Name, options.Filters) + tpl.RawToken = rawTokens[tpl.Claims.IDToken.JWTIdentifier] + tpl.AuthorizeCodeURL = acURLs[tpl.Claims.IDToken.JWTIdentifier].String() } res.Header().Add("Content-Type", "text/html") @@ -127,7 +133,7 @@ func protectedHandler(basic bool) http.HandlerFunc { tpl.Vars.ProtectedSecret = "2511140547" tpl.Vars.Type = "basic" } else { - tpl.Claims = session.Values["claims"].(Claims) + tpl.Claims = session.Values["id_token"].(Claims) hash := sha512.New() hash.Write([]byte(tpl.Vars.Value)) @@ -212,24 +218,39 @@ func oauthCallbackHandler(res http.ResponseWriter, req *http.Request) { } // Extract custom claims - claims := Claims{} + claimsIDToken := Claims{} - var session *sessions.Session + if err = idToken.Claims(&claimsIDToken); err != nil { + writeErr(res, err, "unable to decode id token claims", http.StatusInternalServerError) + return + } + + var userinfo *oidc.UserInfo + + if userinfo, err = provider.UserInfo(req.Context(), oauth2.StaticTokenSource(token)); err != nil { + writeErr(res, err, "unable to retrieve userinfo claims", http.StatusInternalServerError) + return + } + + claimsUserInfo := Claims{} - if err = idToken.Claims(&claims); err != nil { - writeErr(res, err, "unable to retrieve id token claims", http.StatusInternalServerError) + if err = userinfo.Claims(&claimsUserInfo); err != nil { + writeErr(res, err, "unable to decode userinfo claims", http.StatusInternalServerError) return } + var session *sessions.Session + if session, err = store.Get(req, options.CookieName); err != nil { writeErr(res, err, "unable to get session from cookie", http.StatusInternalServerError) return } - session.Values["claims"] = claims + session.Values["id_token"] = claimsIDToken + session.Values["userinfo"] = claimsUserInfo session.Values["logged"] = true - rawTokens[claims.JWTIdentifier] = idTokenRaw - acURLs[claims.JWTIdentifier] = req.URL + rawTokens[claimsIDToken.JWTIdentifier] = idTokenRaw + acURLs[claimsIDToken.JWTIdentifier] = req.URL if err = session.Save(req, res); err != nil { writeErr(res, err, "unable to save session", http.StatusInternalServerError) diff --git a/main.go b/main.go index d55d25d..b1e0793 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/gob" "fmt" + "github.com/gorilla/sessions" "net/http" "net/url" "os" @@ -12,7 +13,6 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/gorilla/mux" - "github.com/gorilla/sessions" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -21,11 +21,12 @@ import ( var options Options -var provider *oidc.Provider -var verifier *oidc.IDTokenVerifier -var store = sessions.NewCookieStore([]byte("secret-key")) - -var oauth2Config oauth2.Config +var ( + provider *oidc.Provider + oauth2Config oauth2.Config + verifier *oidc.IDTokenVerifier + store = sessions.NewCookieStore([]byte("secret-key")) +) var ( rawTokens = make(map[string]string) diff --git a/templates.go b/templates.go index a6c5406..989b243 100644 --- a/templates.go +++ b/templates.go @@ -25,11 +25,16 @@ type indexTplData struct { Error string LoggedIn bool - Claims Claims + Claims tplClaims Groups []string AuthorizeCodeURL string } +type tplClaims struct { + IDToken Claims + UserInfo Claims +} + type protectedTplData struct { Title, Description string Vars struct { diff --git a/templates/index.tpl b/templates/index.tpl index b3a3e8c..996e818 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -20,27 +20,60 @@
Logged in as {{ or .Claims.PreferredUsername .Claims.Subject "unknown" }}!
+Logged in as {{ or .Claims.UserInfo.PreferredUsername .Claims.IDToken.Subject "unknown" }}!
-Access Token Hash: {{ .Claims.AccessTokenHash }}
-Code Hash: {{ .Claims.CodeHash }}
-Authentication Context Class Reference: {{ .Claims.AuthenticationContextClassReference }}
-Authentication Methods Reference: {{ stringsJoin .Claims.AuthenticationMethodsReference ", " }}
-Audience: {{ stringsJoin .Claims.Audience ", " }}
-Expires: {{ .Claims.Expires }}
-Issue Time: {{ .Claims.IssueTime }}
-Requested At: {{ .Claims.RequestedAt }}
-Authorize Time: {{ .Claims.AuthorizeTime }}
-Not Before: {{ .Claims.NotBefore }}
-Issuer: {{ .Claims.Issuer }}
-JWT ID: {{ .Claims.JWTIdentifier }}
-Subject: {{ .Claims.Subject }}
-Preferred Username: {{ .Claims.PreferredUsername }}
-Nonce: {{ .Claims.Nonce }}
-Email: {{ .Claims.Email }}
-Email Verified: {{ .Claims.EmailVerified }}
+Access Token Hash: {{ .Claims.IDToken.AccessTokenHash }}
+Code Hash: {{ .Claims.IDToken.CodeHash }}
+Authentication Context Class Reference: {{ .Claims.IDToken.AuthenticationContextClassReference }}
+Authentication Methods Reference: {{ stringsJoin .Claims.IDToken.AuthenticationMethodsReference ", " }}
+Audience: {{ stringsJoin .Claims.IDToken.Audience ", " }}
+Expires: {{ .Claims.IDToken.Expires }}
+Issue Time: {{ .Claims.IDToken.IssueTime }}
+Requested At: {{ .Claims.IDToken.RequestedAt }}
+Authorize Time: {{ .Claims.IDToken.AuthorizeTime }}
+Not Before: {{ .Claims.IDToken.NotBefore }}
+Issuer: {{ .Claims.IDToken.Issuer }}
+JWT ID: {{ .Claims.IDToken.JWTIdentifier }}
+Subject: {{ .Claims.IDToken.Subject }}
+Nonce: {{ .Claims.IDToken.Nonce }}
+Name: {{ .Claims.UserInfo.Name }}
+Name (ID Token): {{ .Claims.IDToken.Name }}
+Given Name: {{ .Claims.UserInfo.GivenName }}
+Given Name (ID Token): {{ .Claims.IDToken.GivenName }}
+Family Name: {{ .Claims.UserInfo.FamilyName }}
+Family Name (ID Token): {{ .Claims.IDToken.FamilyName }}
+Middle Name: {{ .Claims.UserInfo.MiddleName }}
+Middle Name (ID Token): {{ .Claims.IDToken.MiddleName }}
+Nickname: {{ .Claims.UserInfo.Nickname }}
+Nickname (ID Token): {{ .Claims.IDToken.Nickname }}
+Preferred Username: {{ .Claims.UserInfo.PreferredUsername }}
+Preferred Username (ID Token): {{ .Claims.IDToken.PreferredUsername }}
+Profile: {{ .Claims.UserInfo.Profile }}
+Profile (ID Token): {{ .Claims.IDToken.Profile }}
+Website: {{ .Claims.UserInfo.Website }}
+Website (ID Token): {{ .Claims.IDToken.Website }}
+Gender: {{ .Claims.UserInfo.Gender }}
+Gender (ID Token): {{ .Claims.IDToken.Gender }}
+Birthdate: {{ .Claims.UserInfo.Birthdate }}
+Birthdate (ID Token): {{ .Claims.IDToken.Birthdate }}
+ZoneInfo: {{ .Claims.UserInfo.ZoneInfo }}
+ZoneInfo (ID Token): {{ .Claims.IDToken.ZoneInfo }}
+Locale: {{ .Claims.UserInfo.Locale }}
+Locale (ID Token): {{ .Claims.IDToken.Locale }}
+Updated At: {{ .Claims.UserInfo.UpdatedAt }}
+Updated At (ID Token): {{ .Claims.IDToken.UpdatedAt }}
+Email: {{ .Claims.UserInfo.EmailAlts }}
+Email (ID Token): {{ .Claims.IDToken.EmailAlts }}
+Email Alts: {{ .Claims.UserInfo.Email }}
+Email Alts (ID Token): {{ .Claims.IDToken.Email }}
+Email Verified: {{ .Claims.UserInfo.EmailVerified }}
+Email Verified (ID Token): {{ .Claims.IDToken.EmailVerified }}
+Phone Number: {{ .Claims.UserInfo.PhoneNumber }}
+Phone Number (ID Token): {{ .Claims.IDToken.PhoneNumber }}
+Phone Number Verified: {{ .Claims.UserInfo.PhoneNumberVerified }}
+Phone Number Verified (ID Token): {{ .Claims.IDToken.PhoneNumberVerified }}
Groups: {{ stringsJoin .Groups ", " }}
-Name: {{ .Claims.Name }}
+Groups (ID Token): {{ stringsJoin .Groups ", " }}
Raw: {{ .RawToken }}
Authorize Code URL: {{ .AuthorizeCodeURL }}
{{- else }} diff --git a/templates/protected.tpl b/templates/protected.tpl index a219f51..fc7d148 100644 --- a/templates/protected.tpl +++ b/templates/protected.tpl @@ -21,8 +21,8 @@This is the protected user endpoint
- {{- if stringsEqualFold .Claims.PreferredUsername .Vars.Value }} -Access Granted. Your username is '{{ .Claims.PreferredUsername }}'.
+ {{- if stringsEqualFold .Claims.IDToken.PreferredUsername .Vars.Value }} +Access Granted. Your username is '{{ .Claims.IDToken.PreferredUsername }}'.
1
{{ .Vars.ProtectedSecret }}
{{- else }} @@ -31,7 +31,7 @@ {{- end }} {{- else if eq .Vars.Type "group" }}This is the protected group endpoint
- {{- if (isStringInSlice .Vars.Value .Claims.Groups) }} + {{- if (isStringInSlice .Vars.Value .Claims.IDToken.Groups) }}Access Granted. You have the group '{{ .Vars.Value }}'.
1
{{ .Vars.ProtectedSecret }}
diff --git a/types.go b/types.go index f53a342..cada230 100644 --- a/types.go +++ b/types.go @@ -1,27 +1,52 @@ package main type Claims struct { - AccessTokenHash string `json:"at_hash"` - CodeHash string `json:"c_hash"` - AuthenticationContextClassReference string `json:"acr"` - AuthenticationMethodsReference []string `json:"amr"` - Audience []string `json:"aud"` + JWTIdentifier string `json:"jti"` + Issuer string `json:"iss"` + Subject string `json:"sub"` + Nonce string `json:"nonce"` Expires int64 `json:"exp"` IssueTime int64 `json:"iat"` RequestedAt int64 `json:"rat"` AuthorizeTime int64 `json:"auth_time"` NotBefore int64 `json:"nbf"` - Issuer string `json:"iss"` + Audience []string `json:"aud"` Scope []string `json:"scp"` ScopeString string `json:"scope"` - JWTIdentifier string `json:"jti"` - Subject string `json:"sub"` - Nonce string `json:"nonce"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - PreferredUsername string `json:"preferred_username"` - Groups []string `json:"groups"` - Name string `json:"name"` + AccessTokenHash string `json:"at_hash"` + CodeHash string `json:"c_hash"` + AuthenticationContextClassReference string `json:"acr"` + AuthenticationMethodsReference []string `json:"amr"` + + Name string `json:"name"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + MiddleName string `json:"middle_name"` + Nickname string `json:"nickname"` + PreferredUsername string `json:"preferred_username"` + Profile string `jsoon:"profile"` + Picture string `json:"picture"` + Website string `json:"website"` + Gender string `json:"gender"` + Birthdate string `json:"birthdate"` + ZoneInfo string `json:"zoneinfo"` + Locale string `json:"locale"` + UpdatedAt int64 `json:"updated_at"` + Email string `json:"email"` + EmailAlts []string `json:"alt_emails"` + EmailVerified bool `json:"email_verified"` + PhoneNumber string `json:"phone_number"` + PhoneNumberVerified bool `json:"phone_number_verified"` + Address ClamsAddress `json:"address"` + Groups []string `json:"groups"` +} + +type ClamsAddress struct { + StreetAddress string `json:"street_address"` + Locality string `json:"locality"` + Region string `json:"region"` + PostalCode string `json:"postal_code"` + Country string `json:"country"` } type Options struct {