Skip to content

Commit

Permalink
feat: userinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
james-d-elliott committed Jul 13, 2024
1 parent c888009 commit 9652db5
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 62 deletions.
59 changes: 40 additions & 19 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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")
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 7 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
"crypto/tls"
"encoding/gob"
"fmt"
"github.com/gorilla/sessions"
"net/http"
"net/url"
"os"
"strings"

"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"
Expand All @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
71 changes: 52 additions & 19 deletions templates/index.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,60 @@
<body>
<div id="container">
{{- if .LoggedIn }}
<p id="welcome">Logged in as {{ or .Claims.PreferredUsername .Claims.Subject "unknown" }}!</p>
<p id="welcome">Logged in as {{ or .Claims.UserInfo.PreferredUsername .Claims.IDToken.Subject "unknown" }}!</p>
<p><a href="/logout" id="log-out">Log out</a></p>
<p>Access Token Hash: <span id="claim-at_hash">{{ .Claims.AccessTokenHash }}</span></p>
<p>Code Hash: <span id="claim-c_hash">{{ .Claims.CodeHash }}</span></p>
<p>Authentication Context Class Reference: <span id="claim-acr">{{ .Claims.AuthenticationContextClassReference }}</span></p>
<p>Authentication Methods Reference: <span id="claim-amr">{{ stringsJoin .Claims.AuthenticationMethodsReference ", " }}</span></p>
<p>Audience: <span id="claim-aud">{{ stringsJoin .Claims.Audience ", " }}</span></p>
<p>Expires: <span id="claim-exp">{{ .Claims.Expires }}</span></p>
<p>Issue Time: <span id="claim-iat">{{ .Claims.IssueTime }}</span></p>
<p>Requested At: <span id="claim-rat">{{ .Claims.RequestedAt }}</span></p>
<p>Authorize Time: <span id="claim-auth_at">{{ .Claims.AuthorizeTime }}</span></p>
<p>Not Before: <span id="claim-nbf">{{ .Claims.NotBefore }}</span></p>
<p>Issuer: <span id="claim-iss">{{ .Claims.Issuer }}</span></p>
<p>JWT ID: <span id="claim-jti">{{ .Claims.JWTIdentifier }}</span></p>
<p>Subject: <span id="claim-sub">{{ .Claims.Subject }}</span></p>
<p>Preferred Username: <span id="claim-preferred_username">{{ .Claims.PreferredUsername }}</span></p>
<p>Nonce: <span id="claim-nonce">{{ .Claims.Nonce }}</span></p>
<p>Email: <span id="claim-email">{{ .Claims.Email }}</span></p>
<p>Email Verified: <span id="claim-email_verified">{{ .Claims.EmailVerified }}</span></p>
<p>Access Token Hash: <span id="claim-at_hash">{{ .Claims.IDToken.AccessTokenHash }}</span></p>
<p>Code Hash: <span id="claim-c_hash">{{ .Claims.IDToken.CodeHash }}</span></p>
<p>Authentication Context Class Reference: <span id="claim-acr">{{ .Claims.IDToken.AuthenticationContextClassReference }}</span></p>
<p>Authentication Methods Reference: <span id="claim-amr">{{ stringsJoin .Claims.IDToken.AuthenticationMethodsReference ", " }}</span></p>
<p>Audience: <span id="claim-aud">{{ stringsJoin .Claims.IDToken.Audience ", " }}</span></p>
<p>Expires: <span id="claim-exp">{{ .Claims.IDToken.Expires }}</span></p>
<p>Issue Time: <span id="claim-iat">{{ .Claims.IDToken.IssueTime }}</span></p>
<p>Requested At: <span id="claim-rat">{{ .Claims.IDToken.RequestedAt }}</span></p>
<p>Authorize Time: <span id="claim-auth_at">{{ .Claims.IDToken.AuthorizeTime }}</span></p>
<p>Not Before: <span id="claim-nbf">{{ .Claims.IDToken.NotBefore }}</span></p>
<p>Issuer: <span id="claim-iss">{{ .Claims.IDToken.Issuer }}</span></p>
<p>JWT ID: <span id="claim-jti">{{ .Claims.IDToken.JWTIdentifier }}</span></p>
<p>Subject: <span id="claim-sub">{{ .Claims.IDToken.Subject }}</span></p>
<p>Nonce: <span id="claim-nonce">{{ .Claims.IDToken.Nonce }}</span></p>
<p>Name: <span id="claim-name">{{ .Claims.UserInfo.Name }}</span></p>
<p>Name (ID Token): <span id="claim-id-token-name">{{ .Claims.IDToken.Name }}</span></p>
<p>Given Name: <span id="claim-given_name">{{ .Claims.UserInfo.GivenName }}</span></p>
<p>Given Name (ID Token): <span id="claim-id-token-given_name">{{ .Claims.IDToken.GivenName }}</span></p>
<p>Family Name: <span id="claim-family_name">{{ .Claims.UserInfo.FamilyName }}</span></p>
<p>Family Name (ID Token): <span id="claim-id-token-family_name">{{ .Claims.IDToken.FamilyName }}</span></p>
<p>Middle Name: <span id="claim-middle_name">{{ .Claims.UserInfo.MiddleName }}</span></p>
<p>Middle Name (ID Token): <span id="claim-id-token-middle_name">{{ .Claims.IDToken.MiddleName }}</span></p>
<p>Nickname: <span id="claim-nickname">{{ .Claims.UserInfo.Nickname }}</span></p>
<p>Nickname (ID Token): <span id="claim-id-token-nickname">{{ .Claims.IDToken.Nickname }}</span></p>
<p>Preferred Username: <span id="claim-preferred_username">{{ .Claims.UserInfo.PreferredUsername }}</span></p>
<p>Preferred Username (ID Token): <span id="claim-id-token-preferred_username">{{ .Claims.IDToken.PreferredUsername }}</span></p>
<p>Profile: <span id="claim-profile">{{ .Claims.UserInfo.Profile }}</span></p>
<p>Profile (ID Token): <span id="claim-id-token-profile">{{ .Claims.IDToken.Profile }}</span></p>
<p>Website: <span id="claim-website">{{ .Claims.UserInfo.Website }}</span></p>
<p>Website (ID Token): <span id="claim-id-token-website">{{ .Claims.IDToken.Website }}</span></p>
<p>Gender: <span id="claim-gender">{{ .Claims.UserInfo.Gender }}</span></p>
<p>Gender (ID Token): <span id="claim-id-token-gender">{{ .Claims.IDToken.Gender }}</span></p>
<p>Birthdate: <span id="claim-birthdate">{{ .Claims.UserInfo.Birthdate }}</span></p>
<p>Birthdate (ID Token): <span id="claim-id-token-birthdate">{{ .Claims.IDToken.Birthdate }}</span></p>
<p>ZoneInfo: <span id="claim-zoneinfo">{{ .Claims.UserInfo.ZoneInfo }}</span></p>
<p>ZoneInfo (ID Token): <span id="claim-id-token-zoneinfo">{{ .Claims.IDToken.ZoneInfo }}</span></p>
<p>Locale: <span id="claim-locale">{{ .Claims.UserInfo.Locale }}</span></p>
<p>Locale (ID Token): <span id="claim-id-token-locale">{{ .Claims.IDToken.Locale }}</span></p>
<p>Updated At: <span id="claim-updated_at">{{ .Claims.UserInfo.UpdatedAt }}</span></p>
<p>Updated At (ID Token): <span id="claim-id-token-updated_at">{{ .Claims.IDToken.UpdatedAt }}</span></p>
<p>Email: <span id="claim-email">{{ .Claims.UserInfo.EmailAlts }}</span></p>
<p>Email (ID Token): <span id="claim-id-token-email">{{ .Claims.IDToken.EmailAlts }}</span></p>
<p>Email Alts: <span id="claim-alt_emails">{{ .Claims.UserInfo.Email }}</span></p>
<p>Email Alts (ID Token): <span id="claim-id-token-alt_emails">{{ .Claims.IDToken.Email }}</span></p>
<p>Email Verified: <span id="claim-email_verified">{{ .Claims.UserInfo.EmailVerified }}</span></p>
<p>Email Verified (ID Token): <span id="claim-id-token-email_verified">{{ .Claims.IDToken.EmailVerified }}</span></p>
<p>Phone Number: <span id="claim-phone_number">{{ .Claims.UserInfo.PhoneNumber }}</span></p>
<p>Phone Number (ID Token): <span id="claim-id-token-phone_number">{{ .Claims.IDToken.PhoneNumber }}</span></p>
<p>Phone Number Verified: <span id="claim-phone_number_verified">{{ .Claims.UserInfo.PhoneNumberVerified }}</span></p>
<p>Phone Number Verified (ID Token): <span id="claim-id-token-phone_number_verified">{{ .Claims.IDToken.PhoneNumberVerified }}</span></p>
<p>Groups: <span id="claim-groups">{{ stringsJoin .Groups ", " }}</span></p>
<p>Name: <span id="claim-name">{{ .Claims.Name }}</span></p>
<p>Groups (ID Token): <span id="claim-id-token-groups">{{ stringsJoin .Groups ", " }}</span></p>
<p>Raw: <span id="raw">{{ .RawToken }}</span></p>
<p>Authorize Code URL: <span id="auth-code-url">{{ .AuthorizeCodeURL }}</span></p>
{{- else }}
Expand Down
6 changes: 3 additions & 3 deletions templates/protected.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
<div id="container">
{{- if eq .Vars.Type "user" }}
<p id="message">This is the protected user endpoint</p>
{{- if stringsEqualFold .Claims.PreferredUsername .Vars.Value }}
<p id="message-grant">Access Granted. Your username is '<span id="user">{{ .Claims.PreferredUsername }}</span>'.</p>
{{- if stringsEqualFold .Claims.IDToken.PreferredUsername .Vars.Value }}
<p id="message-grant">Access Granted. Your username is '<span id="user">{{ .Claims.IDToken.PreferredUsername }}</span>'.</p>
<p id="access-granted">1</p>
<p id="protected-secret">{{ .Vars.ProtectedSecret }}</p>
{{- else }}
Expand All @@ -31,7 +31,7 @@
{{- end }}
{{- else if eq .Vars.Type "group" }}
<p id="message">This is the protected group endpoint</p>
{{- if (isStringInSlice .Vars.Value .Claims.Groups) }}
{{- if (isStringInSlice .Vars.Value .Claims.IDToken.Groups) }}
<p id="grant-message">Access Granted. You have the group '<span id="group">{{ .Vars.Value }}</span>'.</p>
<p id="access-granted">1</p>
<p id="protected-secret">{{ .Vars.ProtectedSecret }}</p>
Expand Down
53 changes: 39 additions & 14 deletions types.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down

0 comments on commit 9652db5

Please sign in to comment.