BREAKING CHANGE: - The various verifier types are merged into a oidc.Verifir. - oidc.Verfier became a struct with exported fields * use type aliases for oidc.Verifier this binds the correct contstructor to each verifier usecase. * fix: handle the zero cases for oidc.Time * add unit tests to oidc verifier * fix: correct returned field for JWTTokenRequest JWTTokenRequest.GetIssuedAt() was returning the ExpiresAt field. This change corrects that by returning IssuedAt instead.
89 lines
2.5 KiB
Go
89 lines
2.5 KiB
Go
package op
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
)
|
|
|
|
type UserinfoProvider interface {
|
|
Decoder() httphelper.Decoder
|
|
Crypto() Crypto
|
|
Storage() Storage
|
|
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
|
}
|
|
|
|
func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
Userinfo(w, r, userinfoProvider)
|
|
}
|
|
}
|
|
|
|
func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) {
|
|
accessToken, err := ParseUserinfoRequest(r, userinfoProvider.Decoder())
|
|
if err != nil {
|
|
http.Error(w, "access token missing", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
tokenID, subject, ok := getTokenIDAndSubject(r.Context(), userinfoProvider, accessToken)
|
|
if !ok {
|
|
http.Error(w, "access token invalid", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
info := new(oidc.UserInfo)
|
|
err = userinfoProvider.Storage().SetUserinfoFromToken(r.Context(), info, tokenID, subject, r.Header.Get("origin"))
|
|
if err != nil {
|
|
httphelper.MarshalJSONWithStatus(w, err, http.StatusForbidden)
|
|
return
|
|
}
|
|
httphelper.MarshalJSON(w, info)
|
|
}
|
|
|
|
func ParseUserinfoRequest(r *http.Request, decoder httphelper.Decoder) (string, error) {
|
|
accessToken, err := getAccessToken(r)
|
|
if err == nil {
|
|
return accessToken, nil
|
|
}
|
|
err = r.ParseForm()
|
|
if err != nil {
|
|
return "", errors.New("unable to parse request")
|
|
}
|
|
req := new(oidc.UserInfoRequest)
|
|
err = decoder.Decode(req, r.Form)
|
|
if err != nil {
|
|
return "", errors.New("unable to parse request")
|
|
}
|
|
return req.AccessToken, nil
|
|
}
|
|
|
|
func getAccessToken(r *http.Request) (string, error) {
|
|
authHeader := r.Header.Get("authorization")
|
|
if authHeader == "" {
|
|
return "", errors.New("no auth header")
|
|
}
|
|
parts := strings.Split(authHeader, "Bearer ")
|
|
if len(parts) != 2 {
|
|
return "", errors.New("invalid auth header")
|
|
}
|
|
return parts[1], nil
|
|
}
|
|
|
|
func getTokenIDAndSubject(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, bool) {
|
|
tokenIDSubject, err := userinfoProvider.Crypto().Decrypt(accessToken)
|
|
if err == nil {
|
|
splitToken := strings.Split(tokenIDSubject, ":")
|
|
if len(splitToken) != 2 {
|
|
return "", "", false
|
|
}
|
|
return splitToken[0], splitToken[1], true
|
|
}
|
|
accessTokenClaims, err := VerifyAccessToken[*oidc.AccessTokenClaims](ctx, accessToken, userinfoProvider.AccessTokenVerifier(ctx))
|
|
if err != nil {
|
|
return "", "", false
|
|
}
|
|
return accessTokenClaims.JWTID, accessTokenClaims.Subject, true
|
|
}
|