perf(authZ): improve oidc session check (#8091)

# Which Problems Are Solved

Access token checks make sure that there have not been any termination
events (user locked, deactivated, signed out, ...) in the meantime. This
events were filtered based on the creation date of the last session
event, which might cause latency issues in the database.

# How the Problems Are Solved

- Changed the query to use `position` instead of `created_at`.
- removed `AwaitOpenTransactions`

# Additional Changes

Added the `position` field to the `ReadModel`.

# Additional Context

- relates to #8088
- part of #7639
- backport to 2.53.x
This commit is contained in:
Livio Spring
2024-06-12 11:11:36 +02:00
committed by GitHub
parent 448f8f2c11
commit 931a6c7cce
2 changed files with 10 additions and 11 deletions

View File

@@ -13,6 +13,7 @@ type ReadModel struct {
Events []Event `json:"-"` Events []Event `json:"-"`
ResourceOwner string `json:"-"` ResourceOwner string `json:"-"`
InstanceID string `json:"-"` InstanceID string `json:"-"`
Position float64 `json:"-"`
} }
// AppendEvents adds all the events to the read model. // AppendEvents adds all the events to the read model.
@@ -43,6 +44,7 @@ func (rm *ReadModel) Reduce() error {
} }
rm.ChangeDate = rm.Events[len(rm.Events)-1].CreatedAt() rm.ChangeDate = rm.Events[len(rm.Events)-1].CreatedAt()
rm.ProcessedSequence = rm.Events[len(rm.Events)-1].Sequence() rm.ProcessedSequence = rm.Events[len(rm.Events)-1].Sequence()
rm.Position = rm.Events[len(rm.Events)-1].Position()
// all events processed and not needed anymore // all events processed and not needed anymore
rm.Events = rm.Events[0:0] rm.Events = rm.Events[0:0]
return nil return nil

View File

@@ -17,7 +17,7 @@ import (
) )
type OIDCSessionAccessTokenReadModel struct { type OIDCSessionAccessTokenReadModel struct {
eventstore.WriteModel eventstore.ReadModel
UserID string UserID string
SessionID string SessionID string
@@ -39,7 +39,7 @@ type OIDCSessionAccessTokenReadModel struct {
func newOIDCSessionAccessTokenReadModel(id string) *OIDCSessionAccessTokenReadModel { func newOIDCSessionAccessTokenReadModel(id string) *OIDCSessionAccessTokenReadModel {
return &OIDCSessionAccessTokenReadModel{ return &OIDCSessionAccessTokenReadModel{
WriteModel: eventstore.WriteModel{ ReadModel: eventstore.ReadModel{
AggregateID: id, AggregateID: id,
}, },
} }
@@ -57,13 +57,11 @@ func (wm *OIDCSessionAccessTokenReadModel) Reduce() error {
wm.reduceTokenRevoked(event) wm.reduceTokenRevoked(event)
} }
} }
return wm.WriteModel.Reduce() return wm.ReadModel.Reduce()
} }
func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder { func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AwaitOpenTransactions().
AllowTimeTravel().
AddQuery(). AddQuery().
AggregateTypes(oidcsession.AggregateType). AggregateTypes(oidcsession.AggregateType).
AggregateIDs(wm.AggregateID). AggregateIDs(wm.AggregateID).
@@ -120,7 +118,7 @@ func (q *Queries) ActiveAccessTokenByToken(ctx context.Context, token string) (m
if !model.AccessTokenExpiration.After(time.Now()) { if !model.AccessTokenExpiration.After(time.Now()) {
return nil, zerrors.ThrowPermissionDenied(nil, "QUERY-SAF3rf", "Errors.OIDCSession.Token.Expired") return nil, zerrors.ThrowPermissionDenied(nil, "QUERY-SAF3rf", "Errors.OIDCSession.Token.Expired")
} }
if err = q.checkSessionNotTerminatedAfter(ctx, model.SessionID, model.UserID, model.AccessTokenCreation, model.UserAgent.GetFingerprintID()); err != nil { if err = q.checkSessionNotTerminatedAfter(ctx, model.SessionID, model.UserID, model.Position, model.UserAgent.GetFingerprintID()); err != nil {
return nil, err return nil, err
} }
return model, nil return model, nil
@@ -142,13 +140,13 @@ func (q *Queries) accessTokenByOIDCSessionAndTokenID(ctx context.Context, oidcSe
// checkSessionNotTerminatedAfter checks if a [session.TerminateType] event (or user events leading to a session termination) // checkSessionNotTerminatedAfter checks if a [session.TerminateType] event (or user events leading to a session termination)
// occurred after a certain time and will return an error if so. // occurred after a certain time and will return an error if so.
func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, creation time.Time, fingerprintID string) (err error) { func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, position float64, fingerprintID string) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
model := &sessionTerminatedModel{ model := &sessionTerminatedModel{
sessionID: sessionID, sessionID: sessionID,
creation: creation, position: position,
userID: userID, userID: userID,
fingerPrintID: fingerprintID, fingerPrintID: fingerprintID,
} }
@@ -164,7 +162,7 @@ func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID,
} }
type sessionTerminatedModel struct { type sessionTerminatedModel struct {
creation time.Time position float64
sessionID string sessionID string
userID string userID string
fingerPrintID string fingerPrintID string
@@ -184,8 +182,7 @@ func (s *sessionTerminatedModel) AppendEvents(events ...eventstore.Event) {
func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder { func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AwaitOpenTransactions(). PositionAfter(s.position).
CreationDateAfter(s.creation).
AddQuery(). AddQuery().
AggregateTypes(session.AggregateType). AggregateTypes(session.AggregateType).
AggregateIDs(s.sessionID). AggregateIDs(s.sessionID).