mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:57:33 +00:00
feat(API): support V2 token and session token usage (#6180)
This PR adds support for userinfo and introspection of V2 tokens. Further V2 access tokens and session tokens can be used for authentication on the ZITADEL API (like the current access tokens).
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/repository/authrequest"
|
||||
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
// AddOIDCSessionAccessToken creates a new OIDC Session, creates an access token and returns its id and expiration.
|
||||
@@ -101,6 +102,10 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, authRequestID st
|
||||
if sessionWriteModel.State != domain.SessionStateActive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "OIDCS-sjkl3", "Errors.Session.Terminated")
|
||||
}
|
||||
resourceOwner, err := c.getResourceOwnerOfSessionUser(ctx, sessionWriteModel.UserID, sessionWriteModel.InstanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -114,7 +119,7 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, authRequestID st
|
||||
eventstore: c.eventstore,
|
||||
idGenerator: c.idGenerator,
|
||||
encryptionAlg: c.keyAlgorithm,
|
||||
oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID()),
|
||||
oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, resourceOwner),
|
||||
sessionWriteModel: sessionWriteModel,
|
||||
authRequestWriteModel: authRequestWriteModel,
|
||||
accessTokenLifetime: accessTokenLifetime,
|
||||
@@ -123,6 +128,22 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, authRequestID st
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getResourceOwnerOfSessionUser(ctx context.Context, userID, instanceID string) (string, error) {
|
||||
events, err := c.eventstore.Filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
InstanceID(instanceID).
|
||||
AllowTimeTravel().
|
||||
OrderAsc().
|
||||
Limit(1).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(userID).
|
||||
Builder())
|
||||
if err != nil || len(events) != 1 {
|
||||
return "", caos_errs.ThrowInternal(err, "OIDCS-sferh", "Errors.Internal")
|
||||
}
|
||||
return events[0].Aggregate().ResourceOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) decryptRefreshToken(refreshToken string) (refreshTokenID string, err error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(refreshToken)
|
||||
if err != nil {
|
||||
@@ -144,7 +165,7 @@ func (c *Commands) newOIDCSessionUpdateEvents(ctx context.Context, oidcSessionID
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionWriteModel := NewOIDCSessionWriteModel(oidcSessionID, authz.GetInstance(ctx).InstanceID())
|
||||
sessionWriteModel := NewOIDCSessionWriteModel(oidcSessionID, "")
|
||||
if err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ type OIDCSessionWriteModel struct {
|
||||
AuthMethods []domain.UserAuthMethodType
|
||||
AuthTime time.Time
|
||||
State domain.OIDCSessionState
|
||||
AccessTokenCreation time.Time
|
||||
AccessTokenExpiration time.Time
|
||||
RefreshTokenID string
|
||||
RefreshTokenExpiration time.Time
|
||||
@@ -82,6 +83,11 @@ func (wm *OIDCSessionWriteModel) reduceAdded(e *oidcsession.AddedEvent) {
|
||||
wm.AuthMethods = e.AuthMethods
|
||||
wm.AuthTime = e.AuthTime
|
||||
wm.State = domain.OIDCSessionStateActive
|
||||
// the write model might be initialized without resource owner,
|
||||
// so update the aggregate
|
||||
if wm.ResourceOwner == "" {
|
||||
wm.aggregate = &oidcsession.NewAggregate(wm.AggregateID, e.Aggregate().ResourceOwner).Aggregate
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OIDCSessionWriteModel) reduceAccessTokenAdded(e *oidcsession.AccessTokenAddedEvent) {
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/repository/authrequest"
|
||||
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -108,7 +110,7 @@ func TestCommands_AddOIDCSessionAccessToken(t *testing.T) {
|
||||
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(), // inactive session
|
||||
),
|
||||
},
|
||||
args{
|
||||
@@ -173,15 +175,22 @@ func TestCommands_AddOIDCSessionAccessToken(t *testing.T) {
|
||||
testNow),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username", "firstName", "lastName", "", "", language.English, domain.GenderUnspecified, "", false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
@@ -302,7 +311,7 @@ func TestCommands_AddOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(), // inactive session
|
||||
),
|
||||
},
|
||||
args{
|
||||
@@ -367,19 +376,26 @@ func TestCommands_AddOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
testNow),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username", "firstName", "lastName", "", "", language.English, domain.GenderUnspecified, "", false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
@@ -489,11 +505,11 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
),
|
||||
@@ -515,15 +531,15 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
@@ -545,15 +561,15 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
@@ -561,11 +577,11 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID("instanceID",
|
||||
oidcsession.NewRefreshTokenRenewedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenRenewedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID2", 24*time.Hour),
|
||||
),
|
||||
},
|
||||
@@ -668,11 +684,11 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
),
|
||||
@@ -693,15 +709,15 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
@@ -722,15 +738,15 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"}, []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "instanceID").Aggregate,
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
|
Reference in New Issue
Block a user