mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 11:40:16 +00:00
chore: upgrade to oidc v2 release (#5437)
* chore: upgrade to oidc v2 release * fix tests * fix build errors after rebase * pin oidc v2.1.0 * pin oidc v2.1.1 (include bugfix) * pin oidc v2.1.2 (include bugfix) * pin oidc v2.2.1 (bugfix) include fix zitadel/oidc#349 * fix: refresh token handling * simplify cognitive complexity * fix: handle error --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -92,7 +92,7 @@ func (s *SystemAPIUser) readKey() (*rsa.PublicKey, error) {
|
||||
return crypto.BytesToPublicKey(s.KeyData)
|
||||
}
|
||||
|
||||
func (s *systemJWTStorage) GetKeyByIDAndUserID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
|
||||
func (s *systemJWTStorage) GetKeyByIDAndClientID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
|
||||
cachedKey, ok := s.cachedKeys[userID]
|
||||
if ok {
|
||||
return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil
|
||||
|
@@ -143,7 +143,7 @@ func getInfoFromRequest(req op.TokenRequest) (string, string, string, time.Time,
|
||||
}
|
||||
|
||||
func (o *OPStorage) TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (op.RefreshTokenRequest, error) {
|
||||
tokenView, err := o.repo.RefreshTokenByID(ctx, refreshToken)
|
||||
tokenView, err := o.repo.RefreshTokenByToken(ctx, refreshToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (o *OPStorage) TerminateSession(ctx context.Context, userID, clientID strin
|
||||
}
|
||||
|
||||
func (o *OPStorage) RevokeToken(ctx context.Context, token, userID, clientID string) *oidc.Error {
|
||||
refreshToken, err := o.repo.RefreshTokenByID(ctx, token)
|
||||
refreshToken, err := o.repo.RefreshTokenByID(ctx, token, userID)
|
||||
if err == nil {
|
||||
if refreshToken.ClientID != clientID {
|
||||
return oidc.ErrInvalidClient().WithDescription("token was not issued for this client")
|
||||
@@ -203,6 +203,17 @@ func (o *OPStorage) RevokeToken(ctx context.Context, token, userID, clientID str
|
||||
return oidc.ErrServerError().WithParent(err)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetRefreshTokenInfo(ctx context.Context, clientID string, token string) (userID string, tokenID string, err error) {
|
||||
refreshToken, err := o.repo.RefreshTokenByToken(ctx, token)
|
||||
if err != nil {
|
||||
return "", "", op.ErrInvalidRefreshToken
|
||||
}
|
||||
if refreshToken.ClientID != clientID {
|
||||
return "", "", oidc.ErrInvalidClient().WithDescription("token was not issued for this client")
|
||||
}
|
||||
return refreshToken.UserID, refreshToken.ID, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertProjectRoleScopes(ctx context.Context, clientID string, scopes []string) ([]string, error) {
|
||||
for _, scope := range scopes {
|
||||
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
|
||||
|
@@ -67,7 +67,7 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
||||
return ClientFromBusiness(client, o.defaultLoginURL, accessTokenLifetime, idTokenLifetime, allowedScopes)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
||||
func (o *OPStorage) GetKeyByIDAndClientID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
||||
return o.GetKeyByIDAndIssuer(ctx, keyID, userID)
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr
|
||||
return o.command.VerifyAPIClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
}
|
||||
|
||||
func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.UserInfoSetter, tokenID, subject, origin string) (err error) {
|
||||
func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo *oidc.UserInfo, tokenID, subject, origin string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
token, err := o.repo.TokenByIDs(ctx, subject, tokenID)
|
||||
@@ -133,7 +133,7 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User
|
||||
return o.setUserinfo(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes)
|
||||
}
|
||||
|
||||
func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) {
|
||||
func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo *oidc.UserInfo, userID, applicationID string, scopes []string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
if applicationID != "" {
|
||||
@@ -151,7 +151,7 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use
|
||||
return o.setUserinfo(ctx, userInfo, userID, applicationID, scopes)
|
||||
}
|
||||
|
||||
func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error {
|
||||
func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection *oidc.IntrospectionResponse, tokenID, subject, clientID string) error {
|
||||
token, err := o.repo.TokenByIDs(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
@@ -168,19 +168,21 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if aud == clientID || aud == projectID {
|
||||
err := o.setUserinfo(ctx, introspection, token.UserID, clientID, token.Scopes)
|
||||
userInfo := new(oidc.UserInfo)
|
||||
err := o.setUserinfo(ctx, userInfo, subject, clientID, token.Scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
introspection.SetScopes(token.Scopes)
|
||||
introspection.SetClientID(token.ApplicationID)
|
||||
introspection.SetTokenType(oidc.BearerToken)
|
||||
introspection.SetExpiration(token.Expiration)
|
||||
introspection.SetIssuedAt(token.CreationDate)
|
||||
introspection.SetNotBefore(token.CreationDate)
|
||||
introspection.SetAudience(token.Audience)
|
||||
introspection.SetIssuer(op.IssuerFromContext(ctx))
|
||||
introspection.SetJWTID(token.ID)
|
||||
introspection.SetUserInfo(userInfo)
|
||||
introspection.Scope = token.Scopes
|
||||
introspection.ClientID = token.ApplicationID
|
||||
introspection.TokenType = oidc.BearerToken
|
||||
introspection.Expiration = oidc.FromTime(token.Expiration)
|
||||
introspection.IssuedAt = oidc.FromTime(token.CreationDate)
|
||||
introspection.NotBefore = oidc.FromTime(token.CreationDate)
|
||||
introspection.Audience = token.Audience
|
||||
introspection.Issuer = op.IssuerFromContext(ctx)
|
||||
introspection.JWTID = token.ID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -252,7 +254,7 @@ func (o *OPStorage) checkOrgScopes(ctx context.Context, user *query.User, scopes
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) {
|
||||
func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, userID, applicationID string, scopes []string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
user, err := o.query.GetUserByID(ctx, true, userID, false)
|
||||
@@ -263,50 +265,23 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
for _, scope := range scopes {
|
||||
switch scope {
|
||||
case oidc.ScopeOpenID:
|
||||
userInfo.SetSubject(user.ID)
|
||||
userInfo.Subject = user.ID
|
||||
case oidc.ScopeEmail:
|
||||
if user.Human == nil {
|
||||
continue
|
||||
}
|
||||
userInfo.SetEmail(string(user.Human.Email), user.Human.IsEmailVerified)
|
||||
setUserInfoEmail(userInfo, user)
|
||||
case oidc.ScopeProfile:
|
||||
userInfo.SetPreferredUsername(user.PreferredLoginName)
|
||||
userInfo.SetUpdatedAt(user.ChangeDate)
|
||||
if user.Human != nil {
|
||||
userInfo.SetName(user.Human.DisplayName)
|
||||
userInfo.SetFamilyName(user.Human.LastName)
|
||||
userInfo.SetGivenName(user.Human.FirstName)
|
||||
userInfo.SetNickname(user.Human.NickName)
|
||||
userInfo.SetGender(getGender(user.Human.Gender))
|
||||
userInfo.SetLocale(user.Human.PreferredLanguage)
|
||||
userInfo.SetPicture(domain.AvatarURL(o.assetAPIPrefix(ctx), user.ResourceOwner, user.Human.AvatarKey))
|
||||
} else {
|
||||
userInfo.SetName(user.Machine.Name)
|
||||
}
|
||||
o.setUserInfoProfile(ctx, userInfo, user)
|
||||
case oidc.ScopePhone:
|
||||
if user.Human == nil {
|
||||
continue
|
||||
}
|
||||
userInfo.SetPhone(string(user.Human.Phone), user.Human.IsPhoneVerified)
|
||||
setUserInfoPhone(userInfo, user)
|
||||
case oidc.ScopeAddress:
|
||||
//TODO: handle address for human users as soon as implemented
|
||||
case ScopeUserMetaData:
|
||||
userMetaData, err := o.assertUserMetaData(ctx, userID)
|
||||
if err != nil {
|
||||
if err := o.setUserInfoMetadata(ctx, userInfo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(userMetaData) > 0 {
|
||||
userInfo.AppendClaims(ClaimUserMetaData, userMetaData)
|
||||
}
|
||||
case ScopeResourceOwner:
|
||||
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
|
||||
if err != nil {
|
||||
if err := o.setUserInfoResourceOwner(ctx, userInfo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
for claim, value := range resourceOwnerClaims {
|
||||
userInfo.AppendClaims(claim, value)
|
||||
}
|
||||
|
||||
default:
|
||||
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
|
||||
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
|
||||
@@ -316,13 +291,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
userInfo.AppendClaims(domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
|
||||
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
|
||||
if err != nil {
|
||||
if err := o.setUserInfoResourceOwner(ctx, userInfo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
for claim, value := range resourceOwnerClaims {
|
||||
userInfo.AppendClaims(claim, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +310,64 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
return o.userinfoFlows(ctx, user.ResourceOwner, userGrants, userInfo)
|
||||
}
|
||||
|
||||
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userGrants *query.UserGrants, userInfo oidc.UserInfoSetter) error {
|
||||
func (o *OPStorage) setUserInfoProfile(ctx context.Context, userInfo *oidc.UserInfo, user *query.User) {
|
||||
userInfo.PreferredUsername = user.PreferredLoginName
|
||||
userInfo.UpdatedAt = oidc.FromTime(user.ChangeDate)
|
||||
if user.Machine != nil {
|
||||
userInfo.Name = user.Machine.Name
|
||||
return
|
||||
}
|
||||
userInfo.Name = user.Human.DisplayName
|
||||
userInfo.FamilyName = user.Human.LastName
|
||||
userInfo.GivenName = user.Human.FirstName
|
||||
userInfo.Nickname = user.Human.NickName
|
||||
userInfo.Gender = getGender(user.Human.Gender)
|
||||
userInfo.Locale = oidc.NewLocale(user.Human.PreferredLanguage)
|
||||
userInfo.Picture = domain.AvatarURL(o.assetAPIPrefix(ctx), user.ResourceOwner, user.Human.AvatarKey)
|
||||
}
|
||||
|
||||
func setUserInfoEmail(userInfo *oidc.UserInfo, user *query.User) {
|
||||
if user.Human == nil {
|
||||
return
|
||||
}
|
||||
userInfo.UserInfoEmail = oidc.UserInfoEmail{
|
||||
Email: string(user.Human.Email),
|
||||
EmailVerified: oidc.Bool(user.Human.IsEmailVerified)}
|
||||
}
|
||||
|
||||
func setUserInfoPhone(userInfo *oidc.UserInfo, user *query.User) {
|
||||
if user.Human == nil {
|
||||
return
|
||||
}
|
||||
userInfo.UserInfoPhone = oidc.UserInfoPhone{
|
||||
PhoneNumber: string(user.Human.Phone),
|
||||
PhoneNumberVerified: user.Human.IsPhoneVerified,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OPStorage) setUserInfoMetadata(ctx context.Context, userInfo *oidc.UserInfo, userID string) error {
|
||||
userMetaData, err := o.assertUserMetaData(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(userMetaData) > 0 {
|
||||
userInfo.AppendClaims(ClaimUserMetaData, userMetaData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) setUserInfoResourceOwner(ctx context.Context, userInfo *oidc.UserInfo, userID string) error {
|
||||
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for claim, value := range resourceOwnerClaims {
|
||||
userInfo.AppendClaims(claim, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error {
|
||||
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner, false)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -350,7 +378,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
|
||||
actions.SetFields("claims", userinfoClaims(userInfo)),
|
||||
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
user, err := o.query.GetUserByID(ctx, true, userInfo.GetSubject(), false)
|
||||
user, err := o.query.GetUserByID(ctx, true, userInfo.Subject, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -368,7 +396,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
|
||||
metadata, err := o.query.SearchUserMetadata(
|
||||
ctx,
|
||||
true,
|
||||
userInfo.GetSubject(),
|
||||
userInfo.Subject,
|
||||
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
|
||||
false,
|
||||
)
|
||||
@@ -394,7 +422,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("userinfo",
|
||||
actions.SetFields("setClaim", func(key string, value interface{}) {
|
||||
if userInfo.GetClaim(key) == nil {
|
||||
if userInfo.Claims[key] == nil {
|
||||
userInfo.AppendClaims(key, value)
|
||||
return
|
||||
}
|
||||
@@ -406,7 +434,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
|
||||
),
|
||||
actions.SetFields("claims",
|
||||
actions.SetFields("setClaim", func(key string, value interface{}) {
|
||||
if userInfo.GetClaim(key) == nil {
|
||||
if userInfo.Claims[key] == nil {
|
||||
userInfo.AppendClaims(key, value)
|
||||
return
|
||||
}
|
||||
@@ -434,7 +462,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.GetSubject(), resourceOwner); err != nil {
|
||||
if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.Subject, resourceOwner); err != nil {
|
||||
logging.WithError(err).Info("unable to set md in action")
|
||||
panic(err)
|
||||
}
|
||||
@@ -729,7 +757,7 @@ func appendClaim(claims map[string]interface{}, claim string, value interface{})
|
||||
return claims
|
||||
}
|
||||
|
||||
func userinfoClaims(userInfo oidc.UserInfoSetter) func(c *actions.FieldConfig) interface{} {
|
||||
func userinfoClaims(userInfo *oidc.UserInfo) func(c *actions.FieldConfig) interface{} {
|
||||
return func(c *actions.FieldConfig) interface{} {
|
||||
marshalled, err := json.Marshal(userInfo)
|
||||
if err != nil {
|
||||
|
@@ -84,7 +84,6 @@ func NewProvider(ctx context.Context, config Config, defaultLogoutRedirectURI st
|
||||
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
|
||||
}
|
||||
provider, err := op.NewDynamicOpenIDProvider(
|
||||
ctx,
|
||||
"",
|
||||
opConfig,
|
||||
storage,
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
func (l *Login) runPostExternalAuthenticationActions(
|
||||
user *domain.ExternalUser,
|
||||
tokens *oidc.Tokens,
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims],
|
||||
authRequest *domain.AuthRequest,
|
||||
httpRequest *http.Request,
|
||||
idpUser idp.User,
|
||||
@@ -347,7 +347,7 @@ func (l *Login) runPostCreationActions(
|
||||
return object.UserGrantsToDomain(userID, mutableUserGrants.UserGrants), err
|
||||
}
|
||||
|
||||
func tokenCtxFields(tokens *oidc.Tokens) []actions.FieldOption {
|
||||
func tokenCtxFields(tokens *oidc.Tokens[*oidc.IDTokenClaims]) []actions.FieldOption {
|
||||
var accessToken, idToken string
|
||||
getClaim := func(claim string) interface{} {
|
||||
return nil
|
||||
@@ -367,7 +367,7 @@ func tokenCtxFields(tokens *oidc.Tokens) []actions.FieldOption {
|
||||
idToken = tokens.IDToken
|
||||
if tokens.IDTokenClaims != nil {
|
||||
getClaim = func(claim string) interface{} {
|
||||
return tokens.IDTokenClaims.GetClaim(claim)
|
||||
return tokens.IDTokenClaims.Claims[claim]
|
||||
}
|
||||
claimsJSON = func() (string, error) {
|
||||
c, err := json.Marshal(tokens.IDTokenClaims)
|
||||
|
@@ -837,7 +837,7 @@ func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserG
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens, user idp.User, err error) {
|
||||
func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens[*oidc.IDTokenClaims], user idp.User, err error) {
|
||||
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, user, err); actionErr != nil {
|
||||
logging.WithError(err).Error("both external user authentication and action post authentication failed")
|
||||
}
|
||||
@@ -845,7 +845,7 @@ func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authR
|
||||
}
|
||||
|
||||
// tokens extracts the oidc.Tokens for backwards compatibility of PostExternalAuthenticationActions
|
||||
func tokens(session idp.Session) *oidc.Tokens {
|
||||
func tokens(session idp.Session) *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
switch s := session.(type) {
|
||||
case *openid.Session:
|
||||
return s.Tokens
|
||||
|
@@ -81,7 +81,7 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
session := &jwt.Session{Provider: provider, Tokens: &oidc.Tokens{IDToken: token, Token: &oauth2.Token{}}}
|
||||
session := &jwt.Session{Provider: provider, Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{IDToken: token, Token: &oauth2.Token{}}}
|
||||
user, err := session.FetchUser(r.Context())
|
||||
if err != nil {
|
||||
if _, actionErr := l.runPostExternalAuthenticationActions(new(domain.ExternalUser), tokens(session), authReq, r, user, err); actionErr != nil {
|
||||
|
@@ -26,11 +26,22 @@ type RefreshTokenRepo struct {
|
||||
KeyAlgorithm crypto.EncryptionAlgorithm
|
||||
}
|
||||
|
||||
func (r *RefreshTokenRepo) RefreshTokenByID(ctx context.Context, refreshToken string) (*usr_model.RefreshTokenView, error) {
|
||||
func (r *RefreshTokenRepo) RefreshTokenByToken(ctx context.Context, refreshToken string) (*usr_model.RefreshTokenView, error) {
|
||||
userID, tokenID, token, err := domain.FromRefreshToken(refreshToken, r.KeyAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenView, err := r.RefreshTokenByID(ctx, tokenID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokenView.Token != token {
|
||||
return nil, errors.ThrowNotFound(nil, "EVENT-5Bm9s", "Errors.User.RefreshToken.Invalid")
|
||||
}
|
||||
return tokenView, nil
|
||||
}
|
||||
|
||||
func (r *RefreshTokenRepo) RefreshTokenByID(ctx context.Context, tokenID, userID string) (*usr_model.RefreshTokenView, error) {
|
||||
tokenView, viewErr := r.View.RefreshTokenByID(tokenID, authz.GetInstance(ctx).InstanceID())
|
||||
if viewErr != nil && !errors.IsNotFound(viewErr) {
|
||||
return nil, viewErr
|
||||
@@ -57,7 +68,7 @@ func (r *RefreshTokenRepo) RefreshTokenByID(ctx context.Context, refreshToken st
|
||||
return model.RefreshTokenViewToModel(&viewToken), nil
|
||||
}
|
||||
}
|
||||
if !tokenView.Expiration.After(time.Now()) || tokenView.Token != token {
|
||||
if !tokenView.Expiration.After(time.Now()) {
|
||||
return nil, errors.ThrowNotFound(nil, "EVENT-5Bm9s", "Errors.User.RefreshToken.Invalid")
|
||||
}
|
||||
return model.RefreshTokenViewToModel(tokenView), nil
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RefreshTokenRepository interface {
|
||||
RefreshTokenByID(ctx context.Context, refreshToken string) (*model.RefreshTokenView, error)
|
||||
RefreshTokenByID(ctx context.Context, tokenID, userID string) (*model.RefreshTokenView, error)
|
||||
RefreshTokenByToken(ctx context.Context, refreshToken string) (*model.RefreshTokenView, error)
|
||||
SearchMyRefreshTokens(ctx context.Context, userID string, request *model.RefreshTokenSearchRequest) (*model.RefreshTokenSearchResponse, error)
|
||||
}
|
||||
|
@@ -153,11 +153,11 @@ func (repo *TokenVerifierRepo) getTokenIDAndSubject(ctx context.Context, accessT
|
||||
tokenIDSubject, err := repo.decryptAccessToken(accessToken)
|
||||
if err != nil {
|
||||
// if decryption did not work, it might be a JWT
|
||||
accessTokenClaims, err := op.VerifyAccessToken(ctx, accessToken, repo.jwtTokenVerifier(ctx))
|
||||
accessTokenClaims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, accessToken, repo.jwtTokenVerifier(ctx))
|
||||
if err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
return accessTokenClaims.GetTokenID(), accessTokenClaims.GetSubject(), true
|
||||
return accessTokenClaims.JWTID, accessTokenClaims.Subject, true
|
||||
}
|
||||
splitToken := strings.Split(tokenIDSubject, ":")
|
||||
if len(splitToken) != 2 {
|
||||
|
@@ -30,7 +30,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
options []ProviderOptions
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
type want struct {
|
||||
err func(error) bool
|
||||
@@ -87,7 +87,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
Reply(http.StatusInternalServerError)
|
||||
},
|
||||
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -125,7 +125,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -189,7 +189,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
|
@@ -27,7 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
scopes []string
|
||||
options []oauth.ProviderOpts
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
Reply(http.StatusInternalServerError)
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -122,7 +122,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
|
@@ -27,7 +27,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *openid.Tokens
|
||||
tokens *openid.Tokens[*openid.IDTokenClaims]
|
||||
options []oidc.ProviderOpts
|
||||
}
|
||||
type want struct {
|
||||
@@ -85,7 +85,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://gitlab.com/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens{
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
@@ -122,7 +122,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://gitlab.com/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens{
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
@@ -200,18 +200,26 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func userinfo() openid.UserInfoSetter {
|
||||
info := openid.NewUserInfo()
|
||||
info.SetSubject("sub")
|
||||
info.SetGivenName("firstname")
|
||||
info.SetFamilyName("lastname")
|
||||
info.SetName("firstname lastname")
|
||||
info.SetNickname("nickname")
|
||||
info.SetPreferredUsername("username")
|
||||
info.SetEmail("email", true)
|
||||
info.SetPhone("phone", true)
|
||||
info.SetLocale(language.English)
|
||||
info.SetPicture("picture")
|
||||
info.SetProfile("profile")
|
||||
return info
|
||||
func userinfo() *openid.UserInfo {
|
||||
return &openid.UserInfo{
|
||||
Subject: "sub",
|
||||
UserInfoProfile: openid.UserInfoProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
Name: "firstname lastname",
|
||||
Nickname: "nickname",
|
||||
PreferredUsername: "username",
|
||||
Locale: openid.NewLocale(language.English),
|
||||
Picture: "picture",
|
||||
Profile: "profile",
|
||||
},
|
||||
UserInfoEmail: openid.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: openid.Bool(true),
|
||||
},
|
||||
UserInfoPhone: openid.UserInfoPhone{
|
||||
PhoneNumber: "phone",
|
||||
PhoneNumberVerified: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func New(clientID, clientSecret, redirectURI string, scopes []string, opts ...oi
|
||||
}, nil
|
||||
}
|
||||
|
||||
var userMapper = func(info openid.UserInfo) idp.User {
|
||||
var userMapper = func(info *openid.UserInfo) idp.User {
|
||||
return &User{oidc.DefaultMapper(info)}
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *openid.Tokens
|
||||
tokens *openid.Tokens[*openid.IDTokenClaims]
|
||||
}
|
||||
type want struct {
|
||||
err error
|
||||
@@ -85,7 +85,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://accounts.google.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens{
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
@@ -122,7 +122,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://accounts.google.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens{
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
@@ -201,15 +201,22 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func userinfo() openid.UserInfoSetter {
|
||||
info := openid.NewUserInfo()
|
||||
info.SetSubject("sub")
|
||||
info.SetGivenName("firstname")
|
||||
info.SetFamilyName("lastname")
|
||||
info.SetName("firstname lastname")
|
||||
info.SetEmail("email", true)
|
||||
info.SetLocale(language.English)
|
||||
info.SetPicture("picture")
|
||||
info.AppendClaims("hd", "hosted domain")
|
||||
return info
|
||||
func userinfo() *openid.UserInfo {
|
||||
return &openid.UserInfo{
|
||||
Subject: "sub",
|
||||
UserInfoProfile: openid.UserInfoProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
Name: "firstname lastname",
|
||||
Locale: openid.NewLocale(language.English),
|
||||
Picture: "picture",
|
||||
},
|
||||
UserInfoEmail: openid.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: openid.Bool(true),
|
||||
},
|
||||
Claims: map[string]any{
|
||||
"hd": "hosted domain",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ var (
|
||||
type Session struct {
|
||||
*Provider
|
||||
AuthURL string
|
||||
Tokens *oidc.Tokens
|
||||
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
|
||||
// GetAuthURL implements the [idp.Session] interface
|
||||
@@ -48,12 +48,12 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return &User{s.Tokens.IDTokenClaims}, nil
|
||||
}
|
||||
|
||||
func (s *Session) validateToken(ctx context.Context, token string) (oidc.IDTokenClaims, error) {
|
||||
func (s *Session) validateToken(ctx context.Context, token string) (*oidc.IDTokenClaims, error) {
|
||||
logging.Debug("begin token validation")
|
||||
// TODO: be able to specify them in the template: https://github.com/zitadel/zitadel/issues/5322
|
||||
offset := 3 * time.Second
|
||||
maxAge := time.Hour
|
||||
claims := oidc.EmptyIDTokenClaims()
|
||||
claims := new(oidc.IDTokenClaims)
|
||||
payload, err := oidc.ParseToken(token, claims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: malformed jwt payload: %v", ErrInvalidToken, err)
|
||||
@@ -84,45 +84,57 @@ func (s *Session) validateToken(ctx context.Context, token string) (oidc.IDToken
|
||||
}
|
||||
|
||||
type User struct {
|
||||
oidc.IDTokenClaims
|
||||
*oidc.IDTokenClaims
|
||||
}
|
||||
|
||||
func (u *User) GetID() string {
|
||||
return u.IDTokenClaims.GetSubject()
|
||||
return u.Subject
|
||||
}
|
||||
|
||||
func (u *User) GetFirstName() string {
|
||||
return u.IDTokenClaims.GetGivenName()
|
||||
return u.GivenName
|
||||
}
|
||||
|
||||
func (u *User) GetLastName() string {
|
||||
return u.IDTokenClaims.GetFamilyName()
|
||||
return u.FamilyName
|
||||
}
|
||||
|
||||
func (u *User) GetDisplayName() string {
|
||||
return u.IDTokenClaims.GetName()
|
||||
return u.Name
|
||||
}
|
||||
|
||||
func (u *User) GetNickname() string {
|
||||
return u.IDTokenClaims.GetNickname()
|
||||
return u.Nickname
|
||||
}
|
||||
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return domain.PhoneNumber(u.IDTokenClaims.GetPhoneNumber())
|
||||
}
|
||||
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.IDTokenClaims.IsPhoneNumberVerified()
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.IDTokenClaims.GetLocale()
|
||||
}
|
||||
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.IDTokenClaims.GetPicture()
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.PreferredUsername
|
||||
}
|
||||
|
||||
func (u *User) GetEmail() domain.EmailAddress {
|
||||
return domain.EmailAddress(u.IDTokenClaims.GetEmail())
|
||||
return domain.EmailAddress(u.Email)
|
||||
}
|
||||
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
return bool(u.EmailVerified)
|
||||
}
|
||||
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return domain.PhoneNumber(u.IDTokenClaims.PhoneNumber)
|
||||
}
|
||||
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.PhoneNumberVerified
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.Locale.Tag()
|
||||
}
|
||||
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.Picture
|
||||
}
|
||||
|
||||
func (u *User) GetProfile() string {
|
||||
return u.Profile
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
|
||||
httpMock func(issuer string)
|
||||
authURL string
|
||||
tokens *oidc.Tokens
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
type want struct {
|
||||
err func(error) bool
|
||||
@@ -93,7 +93,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(keys(t))
|
||||
},
|
||||
authURL: "https://auth.com/jwt?authRequestID=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{},
|
||||
IDToken: "invalidToken",
|
||||
},
|
||||
@@ -121,26 +121,32 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(keys(t))
|
||||
},
|
||||
authURL: "https://auth.com/jwt?authRequestID=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{},
|
||||
IDToken: idToken(t, "https://jwt.com"),
|
||||
IDTokenClaims: func() oidc.IDTokenClaims {
|
||||
claims := oidc.EmptyIDTokenClaims()
|
||||
userinfo := oidc.NewUserInfo()
|
||||
userinfo.SetSubject("sub")
|
||||
userinfo.SetPicture("picture")
|
||||
userinfo.SetName("firstname lastname")
|
||||
userinfo.SetEmail("email", true)
|
||||
userinfo.SetGivenName("firstname")
|
||||
userinfo.SetFamilyName("lastname")
|
||||
userinfo.SetNickname("nickname")
|
||||
userinfo.SetPreferredUsername("username")
|
||||
userinfo.SetProfile("profile")
|
||||
userinfo.SetPhone("phone", true)
|
||||
userinfo.SetLocale(language.English)
|
||||
claims.SetUserinfo(userinfo)
|
||||
return claims
|
||||
}(),
|
||||
IDTokenClaims: &oidc.IDTokenClaims{
|
||||
TokenClaims: oidc.TokenClaims{
|
||||
Subject: "sub",
|
||||
},
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
Picture: "picture",
|
||||
Name: "firstname lastname",
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
Nickname: "nickname",
|
||||
PreferredUsername: "username",
|
||||
Profile: "profile",
|
||||
Locale: oidc.NewLocale(language.English),
|
||||
},
|
||||
UserInfoEmail: oidc.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: oidc.Bool(true),
|
||||
},
|
||||
UserInfoPhone: oidc.UserInfoPhone{
|
||||
PhoneNumber: "phone",
|
||||
PhoneNumberVerified: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -219,19 +225,25 @@ func idToken(t *testing.T, issuer string) string {
|
||||
"clientID",
|
||||
0,
|
||||
)
|
||||
info := oidc.NewUserInfo()
|
||||
info.SetSubject("sub")
|
||||
info.SetGivenName("firstname")
|
||||
info.SetFamilyName("lastname")
|
||||
info.SetName("firstname lastname")
|
||||
info.SetNickname("nickname")
|
||||
info.SetPreferredUsername("username")
|
||||
info.SetEmail("email", true)
|
||||
info.SetPhone("phone", true)
|
||||
info.SetLocale(language.English)
|
||||
info.SetPicture("picture")
|
||||
info.SetProfile("profile")
|
||||
claims.SetUserinfo(info)
|
||||
claims.UserInfoProfile = oidc.UserInfoProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
Name: "firstname lastname",
|
||||
Nickname: "nickname",
|
||||
PreferredUsername: "username",
|
||||
Locale: oidc.NewLocale(language.English),
|
||||
Picture: "picture",
|
||||
Profile: "profile",
|
||||
}
|
||||
claims.UserInfoEmail = oidc.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: oidc.Bool(true),
|
||||
}
|
||||
claims.UserInfoPhone = oidc.UserInfoPhone{
|
||||
PhoneNumber: "phone",
|
||||
PhoneNumberVerified: true,
|
||||
}
|
||||
|
||||
privateKey, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAs38btwb3c7r0tMaQpGvBmY+mPwMU/LpfuPoC0k2t4RsKp0fv
|
||||
40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuyrFALIj3Ff1UcKIk0hOH5DDsfh7/q
|
||||
|
@@ -20,7 +20,7 @@ var _ idp.Session = (*Session)(nil)
|
||||
type Session struct {
|
||||
AuthURL string
|
||||
Code string
|
||||
Tokens *oidc.Tokens
|
||||
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
|
||||
Provider *Provider
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (s *Session) authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
}
|
||||
s.Tokens, err = rp.CodeExchange(ctx, s.Code, s.Provider.RelyingParty)
|
||||
s.Tokens, err = rp.CodeExchange[*oidc.IDTokenClaims](ctx, s.Code, s.Provider.RelyingParty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
userMapper func() idp.User
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
type want struct {
|
||||
err func(error) bool
|
||||
@@ -97,7 +97,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
return NewUserMapper("userID")
|
||||
},
|
||||
authURL: "https://oauth2.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -137,7 +137,7 @@ func TestProvider_FetchUser(t *testing.T) {
|
||||
return NewUserMapper("userID")
|
||||
},
|
||||
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
|
@@ -21,7 +21,7 @@ type Provider struct {
|
||||
isAutoCreation bool
|
||||
isAutoUpdate bool
|
||||
useIDToken bool
|
||||
userInfoMapper func(info oidc.UserInfo) idp.User
|
||||
userInfoMapper func(info *oidc.UserInfo) idp.User
|
||||
authOptions []rp.AuthURLOpt
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ func WithSelectAccount() ProviderOpts {
|
||||
}
|
||||
}
|
||||
|
||||
type UserInfoMapper func(info oidc.UserInfo) idp.User
|
||||
type UserInfoMapper func(info *oidc.UserInfo) idp.User
|
||||
|
||||
var DefaultMapper UserInfoMapper = func(info oidc.UserInfo) idp.User {
|
||||
var DefaultMapper UserInfoMapper = func(info *oidc.UserInfo) idp.User {
|
||||
return NewUser(info)
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ func TestProvider_BeginAuth(t *testing.T) {
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
scopes []string
|
||||
userMapper func(info oidc.UserInfo) idp.User
|
||||
userMapper func(info *oidc.UserInfo) idp.User
|
||||
httpMock func(issuer string)
|
||||
opts []ProviderOpts
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func TestProvider_Options(t *testing.T) {
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
scopes []string
|
||||
userMapper func(info oidc.UserInfo) idp.User
|
||||
userMapper func(info *oidc.UserInfo) idp.User
|
||||
opts []ProviderOpts
|
||||
httpMock func(issuer string)
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ type Session struct {
|
||||
Provider *Provider
|
||||
AuthURL string
|
||||
Code string
|
||||
Tokens *oidc.Tokens
|
||||
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
|
||||
// GetAuthURL implements the [idp.Session] interface.
|
||||
@@ -48,7 +48,7 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return nil, err
|
||||
}
|
||||
if s.Provider.useIDToken {
|
||||
info = s.Tokens.IDTokenClaims
|
||||
info = s.Tokens.IDTokenClaims.GetUserInfo()
|
||||
}
|
||||
u := s.Provider.userInfoMapper(info)
|
||||
return u, nil
|
||||
@@ -58,50 +58,66 @@ func (s *Session) authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
}
|
||||
s.Tokens, err = rp.CodeExchange(ctx, s.Code, s.Provider.RelyingParty)
|
||||
s.Tokens, err = rp.CodeExchange[*oidc.IDTokenClaims](ctx, s.Code, s.Provider.RelyingParty)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewUser(info oidc.UserInfo) *User {
|
||||
func NewUser(info *oidc.UserInfo) *User {
|
||||
return &User{UserInfo: info}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
oidc.UserInfo
|
||||
*oidc.UserInfo
|
||||
}
|
||||
|
||||
func (u *User) GetID() string {
|
||||
return u.GetSubject()
|
||||
return u.Subject
|
||||
}
|
||||
|
||||
func (u *User) GetFirstName() string {
|
||||
return u.GetGivenName()
|
||||
return u.GivenName
|
||||
}
|
||||
|
||||
func (u *User) GetLastName() string {
|
||||
return u.GetFamilyName()
|
||||
return u.FamilyName
|
||||
}
|
||||
|
||||
func (u *User) GetDisplayName() string {
|
||||
return u.GetName()
|
||||
return u.Name
|
||||
}
|
||||
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return domain.PhoneNumber(u.GetPhoneNumber())
|
||||
func (u *User) GetNickname() string {
|
||||
return u.Nickname
|
||||
}
|
||||
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.IsPhoneNumberVerified()
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.GetLocale()
|
||||
}
|
||||
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.GetPicture()
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.PreferredUsername
|
||||
}
|
||||
|
||||
func (u *User) GetEmail() domain.EmailAddress {
|
||||
return domain.EmailAddress(u.UserInfo.GetEmail())
|
||||
return domain.EmailAddress(u.UserInfo.Email)
|
||||
}
|
||||
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
return bool(u.EmailVerified)
|
||||
}
|
||||
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return domain.PhoneNumber(u.PhoneNumber)
|
||||
}
|
||||
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.PhoneNumberVerified
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.Locale.Tag()
|
||||
}
|
||||
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.Picture
|
||||
}
|
||||
|
||||
func (u *User) GetProfile() string {
|
||||
return u.Profile
|
||||
}
|
||||
|
@@ -29,11 +29,11 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
scopes []string
|
||||
userMapper func(oidc.UserInfo) idp.User
|
||||
userMapper func(*oidc.UserInfo) idp.User
|
||||
httpMock func(issuer string)
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
type want struct {
|
||||
err error
|
||||
@@ -114,7 +114,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -163,7 +163,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
JSON(userinfo())
|
||||
},
|
||||
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
|
||||
tokens: &oidc.Tokens{
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
@@ -294,20 +294,28 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func userinfo() oidc.UserInfoSetter {
|
||||
info := oidc.NewUserInfo()
|
||||
info.SetSubject("sub")
|
||||
info.SetGivenName("firstname")
|
||||
info.SetFamilyName("lastname")
|
||||
info.SetName("firstname lastname")
|
||||
info.SetNickname("nickname")
|
||||
info.SetPreferredUsername("username")
|
||||
info.SetEmail("email", true)
|
||||
info.SetPhone("phone", true)
|
||||
info.SetLocale(language.English)
|
||||
info.SetPicture("picture")
|
||||
info.SetProfile("profile")
|
||||
return info
|
||||
func userinfo() *oidc.UserInfo {
|
||||
return &oidc.UserInfo{
|
||||
Subject: "sub",
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
Name: "firstname lastname",
|
||||
Nickname: "nickname",
|
||||
PreferredUsername: "username",
|
||||
Locale: oidc.NewLocale(language.English),
|
||||
Picture: "picture",
|
||||
Profile: "profile",
|
||||
},
|
||||
UserInfoEmail: oidc.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: oidc.Bool(true),
|
||||
},
|
||||
UserInfoPhone: oidc.UserInfoPhone{
|
||||
PhoneNumber: "phone",
|
||||
PhoneNumberVerified: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func tokenResponse(t *testing.T, issuer string) *oidc.AccessTokenResponse {
|
||||
|
Reference in New Issue
Block a user