feat(oidc): sid claim for id_tokens issued through login V1 (#8525)

# Which Problems Are Solved

id_tokens issued for auth requests created through the login UI
currently do not provide a sid claim.
This is due to the fact that (SSO) sessions for the login UI do not have
one and are only computed by the userAgent(ID), the user(ID) and the
authentication checks of the latter.

This prevents client to track sessions and terminate specific session on
the end_session_endpoint.

# How the Problems Are Solved

- An `id` column is added to the `auth.user_sessions` table.
- The `id` (prefixed with `V1_`) is set whenever a session is added or
updated to active (from terminated)
- The id is passed to the `oidc session` (as v2 sessionIDs), to expose
it as `sid` claim

# Additional Changes

- refactored `getUpdateCols` to handle different column value types and
add arguments for query

# Additional Context

- closes #8499 
- relates to #8501
This commit is contained in:
Livio Spring
2024-09-03 15:19:00 +02:00
committed by GitHub
parent 41b7bf976d
commit 9ec9ad4314
28 changed files with 336 additions and 54 deletions

View File

@@ -1048,6 +1048,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
if err != nil {
return nil, err
}
request.SessionID = userSession.ID
request.DisplayName = userSession.DisplayName
request.AvatarKey = userSession.AvatarKey
if user.HumanView != nil && user.HumanView.PreferredLanguage != "" {

View File

@@ -9,6 +9,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
handler2 "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/id"
query2 "github.com/zitadel/zitadel/internal/query"
)
@@ -40,6 +41,7 @@ func Register(ctx context.Context, configs Config, view *view.View, queries *que
configs.overwrite("UserSession"),
view,
queries,
id.SonyFlakeGenerator(),
))
projections = append(projections, newToken(ctx,

View File

@@ -2,12 +2,14 @@ package handler
import (
"context"
"slices"
"time"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/id"
query2 "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
@@ -18,12 +20,15 @@ import (
const (
userSessionTable = "auth.user_sessions"
IDPrefixV1 = "V1_"
)
type UserSession struct {
queries *query2.Queries
view *auth_view.View
es handler.EventStore
queries *query2.Queries
view *auth_view.View
es handler.EventStore
idGenerator id.Generator
}
var _ handler.Projection = (*UserSession)(nil)
@@ -33,14 +38,16 @@ func newUserSession(
config handler.Config,
view *auth_view.View,
queries *query2.Queries,
idGenerator id.Generator,
) *handler.Handler {
return handler.NewHandler(
ctx,
&config,
&UserSession{
queries: queries,
view: view,
es: config.Eventstore,
queries: queries,
view: view,
es: config.Eventstore,
idGenerator: idGenerator,
},
)
}
@@ -187,7 +194,7 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
}
}
func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
func (u *UserSession) sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
userAgent, err := agentIDFromSession(event)
if err != nil {
return nil, err
@@ -203,14 +210,34 @@ func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handle
}, columns...), nil
}
func (u *UserSession) sessionColumnsActivate(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
sessionID, err := u.idGenerator.Next()
if err != nil {
return nil, err
}
sessionID = IDPrefixV1 + sessionID
columns = slices.Grow(columns, 2)
columns = append(columns,
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
handler.NewCol(view_model.UserSessionKeyID,
handler.OnlySetValueInCase(userSessionTable, sessionID,
handler.ConditionOr(
handler.ColumnChangedCondition(userSessionTable, view_model.UserSessionKeyState, domain.UserSessionStateTerminated, domain.UserSessionStateActive),
handler.ColumnIsNullCondition(userSessionTable, view_model.UserSessionKeyID),
),
),
),
)
return u.sessionColumns(event, columns...)
}
func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err error) {
// in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() {
case user.UserV1PasswordCheckSucceededType,
user.HumanPasswordCheckSucceededType:
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -218,9 +245,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1PasswordCheckFailedType,
user.HumanPasswordCheckFailedType:
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -228,10 +254,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1MFAOTPCheckSucceededType,
user.HumanMFAOTPCheckSucceededType:
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -240,9 +265,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
case user.UserV1MFAOTPCheckFailedType,
user.HumanMFAOTPCheckFailedType,
user.HumanU2FTokenCheckFailedType:
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -250,7 +274,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1SignedOutType,
user.HumanSignedOutType:
columns, err := sessionColumns(event,
columns, err := u.sessionColumns(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
@@ -270,10 +294,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil {
return nil, err
}
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -285,10 +308,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil {
return nil, err
}
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -300,11 +322,10 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil {
return nil, err
}
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -316,10 +337,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil {
return nil, err
}
columns, err := sessionColumns(event,
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
)
if err != nil {
return nil, err
@@ -429,8 +449,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
},
), nil
case user.HumanRegisteredType:
columns, err := sessionColumns(event,
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
)
if err != nil {