mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 11:04:25 +00:00
85eb2eda0b
fix(oidc); refresh token for device authorization Due to a mis-alignment of OIDC interface and concrete implementations in zitadel, requesting a refresh token for device authorization would fail. This change adds the possibility to to use the op.IDTokenRequest directly. Also, the UserAgentID is dropped as required parameter, as devices do not have a user agent.
196 lines
7.3 KiB
Go
196 lines
7.3 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func (c *Commands) AddAccessAndRefreshToken(
|
|
ctx context.Context,
|
|
orgID,
|
|
agentID,
|
|
clientID,
|
|
userID,
|
|
refreshToken string,
|
|
audience,
|
|
scopes,
|
|
authMethodsReferences []string,
|
|
accessLifetime,
|
|
refreshIdleExpiration,
|
|
refreshExpiration time.Duration,
|
|
authTime time.Time,
|
|
) (accessToken *domain.Token, newRefreshToken string, err error) {
|
|
if refreshToken == "" {
|
|
return c.AddNewRefreshTokenAndAccessToken(ctx, userID, orgID, agentID, clientID, audience, scopes, authMethodsReferences, refreshExpiration, accessLifetime, refreshIdleExpiration, authTime)
|
|
}
|
|
return c.RenewRefreshTokenAndAccessToken(ctx, userID, orgID, refreshToken, agentID, clientID, audience, scopes, refreshIdleExpiration, accessLifetime)
|
|
}
|
|
|
|
func (c *Commands) AddNewRefreshTokenAndAccessToken(
|
|
ctx context.Context,
|
|
userID,
|
|
orgID,
|
|
agentID,
|
|
clientID string,
|
|
audience,
|
|
scopes,
|
|
authMethodsReferences []string,
|
|
refreshExpiration,
|
|
accessLifetime,
|
|
refreshIdleExpiration time.Duration,
|
|
authTime time.Time,
|
|
) (accessToken *domain.Token, newRefreshToken string, err error) {
|
|
if userID == "" || clientID == "" {
|
|
return nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-adg4r", "Errors.IDMissing")
|
|
}
|
|
userWriteModel := NewUserWriteModel(userID, orgID)
|
|
refreshTokenID, err := c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
refreshTokenEvent, newRefreshToken, err := c.addRefreshToken(ctx, accessToken, authMethodsReferences, authTime, refreshIdleExpiration, refreshExpiration)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
_, err = c.eventstore.Push(ctx, accessTokenEvent, refreshTokenEvent)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return accessToken, newRefreshToken, nil
|
|
}
|
|
|
|
func (c *Commands) RenewRefreshTokenAndAccessToken(
|
|
ctx context.Context,
|
|
userID,
|
|
orgID,
|
|
refreshToken,
|
|
agentID,
|
|
clientID string,
|
|
audience,
|
|
scopes []string,
|
|
idleExpiration,
|
|
accessLifetime time.Duration,
|
|
) (accessToken *domain.Token, newRefreshToken string, err error) {
|
|
refreshTokenEvent, refreshTokenID, newRefreshToken, err := c.renewRefreshToken(ctx, userID, orgID, refreshToken, idleExpiration)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
userWriteModel := NewUserWriteModel(userID, orgID)
|
|
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
_, err = c.eventstore.Push(ctx, accessTokenEvent, refreshTokenEvent)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return accessToken, newRefreshToken, nil
|
|
}
|
|
|
|
func (c *Commands) RevokeRefreshToken(ctx context.Context, userID, orgID, tokenID string) (*domain.ObjectDetails, error) {
|
|
removeEvent, refreshTokenWriteModel, err := c.removeRefreshToken(ctx, userID, orgID, tokenID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
events, err := c.eventstore.Push(ctx, removeEvent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(refreshTokenWriteModel, events...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&refreshTokenWriteModel.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) RevokeRefreshTokens(ctx context.Context, userID, orgID string, tokenIDs []string) (err error) {
|
|
if len(tokenIDs) == 0 {
|
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Gfj42", "Errors.IDMissing")
|
|
}
|
|
events := make([]eventstore.Command, len(tokenIDs))
|
|
for i, tokenID := range tokenIDs {
|
|
event, _, err := c.removeRefreshToken(ctx, userID, orgID, tokenID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
events[i] = event
|
|
}
|
|
_, err = c.eventstore.Push(ctx, events...)
|
|
return err
|
|
}
|
|
|
|
func (c *Commands) addRefreshToken(ctx context.Context, accessToken *domain.Token, authMethodsReferences []string, authTime time.Time, idleExpiration, expiration time.Duration) (*user.HumanRefreshTokenAddedEvent, string, error) {
|
|
refreshToken, err := domain.NewRefreshToken(accessToken.AggregateID, accessToken.RefreshTokenID, c.keyAlgorithm)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(accessToken.AggregateID, accessToken.ResourceOwner, accessToken.RefreshTokenID)
|
|
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
|
|
return user.NewHumanRefreshTokenAddedEvent(ctx, userAgg, accessToken.RefreshTokenID, accessToken.ApplicationID, accessToken.UserAgentID,
|
|
accessToken.PreferredLanguage, accessToken.Audience, accessToken.Scopes, authMethodsReferences, authTime, idleExpiration, expiration),
|
|
refreshToken, nil
|
|
}
|
|
|
|
func (c *Commands) renewRefreshToken(ctx context.Context, userID, orgID, refreshToken string, idleExpiration time.Duration) (event *user.HumanRefreshTokenRenewedEvent, refreshTokenID, newRefreshToken string, err error) {
|
|
if refreshToken == "" {
|
|
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-DHrr3", "Errors.IDMissing")
|
|
}
|
|
|
|
tokenUserID, tokenID, token, err := domain.FromRefreshToken(refreshToken, c.keyAlgorithm)
|
|
if err != nil {
|
|
return nil, "", "", zerrors.ThrowInvalidArgument(err, "COMMAND-Dbfe4", "Errors.User.RefreshToken.Invalid")
|
|
}
|
|
if tokenUserID != userID {
|
|
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-Ht2g2", "Errors.User.RefreshToken.Invalid")
|
|
}
|
|
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(userID, orgID, tokenID)
|
|
err = c.eventstore.FilterToQueryReducer(ctx, refreshTokenWriteModel)
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
if refreshTokenWriteModel.UserState != domain.UserStateActive {
|
|
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-BHnhs", "Errors.User.RefreshToken.Invalid")
|
|
}
|
|
if refreshTokenWriteModel.RefreshToken != token ||
|
|
refreshTokenWriteModel.IdleExpiration.Before(time.Now()) ||
|
|
refreshTokenWriteModel.Expiration.Before(time.Now()) {
|
|
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-Vr43e", "Errors.User.RefreshToken.Invalid")
|
|
}
|
|
|
|
newToken, err := c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
newRefreshToken, err = domain.RefreshToken(userID, tokenID, newToken, c.keyAlgorithm)
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
|
|
return user.NewHumanRefreshTokenRenewedEvent(ctx, userAgg, tokenID, newToken, idleExpiration), tokenID, newRefreshToken, nil
|
|
}
|
|
|
|
func (c *Commands) removeRefreshToken(ctx context.Context, userID, orgID, tokenID string) (*user.HumanRefreshTokenRemovedEvent, *HumanRefreshTokenWriteModel, error) {
|
|
if userID == "" || orgID == "" || tokenID == "" {
|
|
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GVDgf", "Errors.IDMissing")
|
|
}
|
|
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(userID, orgID, tokenID)
|
|
err := c.eventstore.FilterToQueryReducer(ctx, refreshTokenWriteModel)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if refreshTokenWriteModel.UserState != domain.UserStateActive {
|
|
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-BHt2w", "Errors.User.RefreshToken.NotFound")
|
|
}
|
|
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
|
|
return user.NewHumanRefreshTokenRemovedEvent(ctx, userAgg, tokenID), refreshTokenWriteModel, nil
|
|
}
|