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 @@
{{- if .LoggedIn }} -

Logged in as {{ or .Claims.PreferredUsername .Claims.Subject "unknown" }}!

+

Logged in as {{ or .Claims.UserInfo.PreferredUsername .Claims.IDToken.Subject "unknown" }}!

Log out

-

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 @@
{{- if eq .Vars.Type "user" }}

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 {