chore: move the go code into a subfolder

This commit is contained in:
Florian Forster
2025-08-05 15:20:32 -07:00
parent 4ad22ba456
commit cd2921de26
2978 changed files with 373 additions and 300 deletions

View File

@@ -0,0 +1,45 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
)
type AuthRequestRepository interface {
CreateAuthRequest(ctx context.Context, request *domain.AuthRequest) (*domain.AuthRequest, error)
AuthRequestByID(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error)
AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error)
AuthRequestByCode(ctx context.Context, code string) (*domain.AuthRequest, error)
SaveAuthCode(ctx context.Context, id, code, userAgentID string) error
SaveSAMLRequestID(ctx context.Context, id, requestID, userAgentID string) error
DeleteAuthRequest(ctx context.Context, id string) error
CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error
CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser, info *domain.BrowserInfo, migrationCheck bool) error
SetExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser) error
SetLinkingUser(ctx context.Context, request *domain.AuthRequest, externalUser *domain.ExternalUser) error
SelectUser(ctx context.Context, authReqID, userID, userAgentID string) error
SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string, idpArguments map[string]any) error
VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error
VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) error
SendMFAOTPSMS(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
VerifyMFAOTPSMS(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
SendMFAOTPEmail(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
VerifyMFAOTPEmail(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
VerifyPasswordlessSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName string, credentialData []byte) (err error)
BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
VerifyPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName, codeID, verificationCode string, credentialData []byte) (err error)
BeginPasswordlessLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error
RequestLocalAuth(ctx context.Context, authReqID, userAgentID string) error
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
package eventstore
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
iam_view_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
"github.com/zitadel/zitadel/internal/query"
)
type OrgRepository struct {
SearchLimit uint64
Eventstore *eventstore.Eventstore
View *auth_view.View
SystemDefaults systemdefaults.SystemDefaults
Query *query.Queries
}
func (repo *OrgRepository) GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) {
policy, err := repo.Query.PasswordComplexityPolicyByOrg(ctx, false, authz.GetCtxData(ctx).OrgID, false)
if err != nil {
return nil, err
}
return iam_view_model.PasswordComplexityViewToModel(policy), err
}
func (repo *OrgRepository) GetLoginText(ctx context.Context, orgID string) ([]*domain.CustomText, error) {
loginTexts, err := repo.Query.CustomTextListByTemplate(ctx, authz.GetInstance(ctx).InstanceID(), domain.LoginCustomText, false)
if err != nil {
return nil, err
}
orgLoginTexts, err := repo.Query.CustomTextListByTemplate(ctx, orgID, domain.LoginCustomText, false)
if err != nil {
return nil, err
}
return append(query.CustomTextsToDomain(loginTexts), query.CustomTextsToDomain(orgLoginTexts)...), nil
}

View File

@@ -0,0 +1,117 @@
package eventstore
import (
"context"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
usr_model "github.com/zitadel/zitadel/internal/user/model"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type RefreshTokenRepo struct {
Eventstore *eventstore.Eventstore
View *view.View
SearchLimit uint64
KeyAlgorithm crypto.EncryptionAlgorithm
}
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, zerrors.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) {
instanceID := authz.GetInstance(ctx).InstanceID()
// always load the latest sequence first, so in case the token was not found by id,
// the sequence will be equal or lower than the actual projection and no events are lost
sequence, err := r.View.GetLatestRefreshTokenSequence(ctx)
logging.WithFields("instanceID", instanceID, "userID", userID, "tokenID", tokenID).
OnError(err).
Errorf("could not get current sequence for RefreshTokenByID")
tokenView, viewErr := r.View.RefreshTokenByID(tokenID, instanceID)
if viewErr != nil && !zerrors.IsNotFound(viewErr) {
return nil, viewErr
}
if zerrors.IsNotFound(viewErr) {
tokenView = new(model.RefreshTokenView)
tokenView.ID = tokenID
tokenView.UserID = userID
tokenView.InstanceID = instanceID
if sequence != nil {
tokenView.ChangeDate = sequence.EventCreatedAt
}
}
events, esErr := r.getUserEvents(ctx, userID, tokenView.InstanceID, tokenView.ChangeDate, tokenView.GetRelevantEventTypes())
if zerrors.IsNotFound(viewErr) && len(events) == 0 {
return nil, zerrors.ThrowNotFound(nil, "EVENT-BHB52", "Errors.User.RefreshToken.Invalid")
}
if esErr != nil {
logging.Log("EVENT-AE462").WithError(viewErr).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error retrieving new events")
return model.RefreshTokenViewToModel(tokenView), nil
}
viewToken := *tokenView
for _, event := range events {
err := tokenView.AppendEventIfMyRefreshToken(event)
if err != nil {
return model.RefreshTokenViewToModel(&viewToken), nil
}
}
if !tokenView.Expiration.After(time.Now()) {
return nil, zerrors.ThrowNotFound(nil, "EVENT-5Bm9s", "Errors.User.RefreshToken.Invalid")
}
return model.RefreshTokenViewToModel(tokenView), nil
}
func (r *RefreshTokenRepo) SearchMyRefreshTokens(ctx context.Context, userID string, request *usr_model.RefreshTokenSearchRequest) (*usr_model.RefreshTokenSearchResponse, error) {
err := request.EnsureLimit(r.SearchLimit)
if err != nil {
return nil, err
}
sequence, err := r.View.GetLatestRefreshTokenSequence(ctx)
logging.Log("EVENT-GBdn4").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest refresh token sequence")
request.Queries = append(request.Queries, &usr_model.RefreshTokenSearchQuery{Key: usr_model.RefreshTokenSearchKeyUserID, Method: domain.SearchMethodEquals, Value: userID})
tokens, count, err := r.View.SearchRefreshTokens(request)
if err != nil {
return nil, err
}
return &usr_model.RefreshTokenSearchResponse{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: count,
Sequence: sequence.Sequence,
Timestamp: sequence.LastRun,
Result: model.RefreshTokenViewsToModel(tokens),
}, nil
}
func (r *RefreshTokenRepo) getUserEvents(ctx context.Context, userID, instanceID string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) {
query, err := usr_view.UserByIDQuery(userID, instanceID, changeDate, eventTypes)
if err != nil {
return nil, err
}
return r.Eventstore.Filter(ctx, query)
}

View File

@@ -0,0 +1,83 @@
package eventstore
import (
"context"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
usr_model "github.com/zitadel/zitadel/internal/user/model"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type TokenRepo struct {
Eventstore *eventstore.Eventstore
View *view.View
}
func (repo *TokenRepo) TokenByIDs(ctx context.Context, userID, tokenID string) (_ *usr_model.TokenView, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
instanceID := authz.GetInstance(ctx).InstanceID()
// always load the latest sequence first, so in case the token was not found by id,
// the sequence will be equal or lower than the actual projection and no events are lost
sequence, err := repo.View.GetLatestTokenSequence(ctx, instanceID)
logging.WithFields("instanceID", instanceID, "userID", userID, "tokenID", tokenID).
OnError(err).
Errorf("could not get current sequence for TokenByIDs")
token, viewErr := repo.View.TokenByIDs(tokenID, userID, instanceID)
if viewErr != nil && !zerrors.IsNotFound(viewErr) {
return nil, viewErr
}
if zerrors.IsNotFound(viewErr) {
token = new(model.TokenView)
token.ID = tokenID
token.UserID = userID
token.InstanceID = instanceID
if sequence != nil {
token.ChangeDate = sequence.EventCreatedAt
}
}
events, esErr := repo.getUserEvents(ctx, userID, token.InstanceID, token.ChangeDate, token.GetRelevantEventTypes())
if zerrors.IsNotFound(viewErr) && len(events) == 0 {
return nil, zerrors.ThrowNotFound(nil, "EVENT-4T90g", "Errors.Token.NotFound")
}
if esErr != nil {
logging.Log("EVENT-5Nm9s").WithError(viewErr).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error retrieving new events")
return model.TokenViewToModel(token), nil
}
viewToken := *token
for _, event := range events {
err := token.AppendEventIfMyToken(event)
if err != nil {
return model.TokenViewToModel(&viewToken), nil
}
}
if !token.Expiration.After(time.Now().UTC()) || token.Deactivated {
return nil, zerrors.ThrowNotFound(nil, "EVENT-5Bm9s", "Errors.Token.NotFound")
}
return model.TokenViewToModel(token), nil
}
func (r *TokenRepo) getUserEvents(ctx context.Context, userID, instanceID string, changeDate time.Time, eventTypes []eventstore.EventType) (_ []eventstore.Event, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, err := usr_view.UserByIDQuery(userID, instanceID, changeDate, eventTypes)
if err != nil {
return nil, err
}
return r.Eventstore.Filter(ctx, query)
}

View File

@@ -0,0 +1,153 @@
package eventstore
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/user"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type UserRepo struct {
SearchLimit uint64
Eventstore *eventstore.Eventstore
View *view.View
Query *query.Queries
SystemDefaults systemdefaults.SystemDefaults
}
func (repo *UserRepo) Health(ctx context.Context) error {
return repo.Eventstore.Health(ctx)
}
func (repo *UserRepo) UserSessionsByAgentID(ctx context.Context, agentID string) ([]command.HumanSignOutSession, error) {
sessions, err := repo.View.UserSessionsByAgentID(ctx, agentID, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
signoutSessions := make([]command.HumanSignOutSession, 0, len(sessions))
for _, session := range sessions {
if session.State.V == domain.UserSessionStateActive && session.ID.Valid {
signoutSessions = append(signoutSessions, command.HumanSignOutSession{
ID: session.ID.String,
UserID: session.UserID,
})
}
}
return signoutSessions, nil
}
func (repo *UserRepo) UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error) {
return repo.View.UserAgentIDBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
}
func (repo *UserRepo) UserSessionByID(ctx context.Context, sessionID string) (*model.UserSessionView, error) {
return repo.View.UserSessionByID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
}
func (repo *UserRepo) ActiveUserSessionsBySessionID(ctx context.Context, sessionID string) (userAgentID string, signoutSessions []command.HumanSignOutSession, err error) {
userAgentID, sessions, err := repo.View.ActiveUserSessionsBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
if err != nil {
return "", nil, err
}
signoutSessions = make([]command.HumanSignOutSession, 0, len(sessions))
for sessionID, userID := range sessions {
signoutSessions = append(signoutSessions, command.HumanSignOutSession{
ID: sessionID,
UserID: userID,
})
}
return userAgentID, signoutSessions, nil
}
func (repo *UserRepo) UserEventsByID(ctx context.Context, id string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) {
query, err := usr_view.UserByIDQuery(id, authz.GetInstance(ctx).InstanceID(), changeDate, eventTypes)
if err != nil {
return nil, err
}
return repo.Eventstore.Filter(ctx, query) //nolint:staticcheck
}
type passwordCodeCheck struct {
userID string
exists bool
events int
}
func (p *passwordCodeCheck) Reduce() error {
p.exists = p.events > 0
return nil
}
func (p *passwordCodeCheck) AppendEvents(events ...eventstore.Event) {
p.events += len(events)
}
func (p *passwordCodeCheck) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(p.userID).
EventTypes(user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType,
user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType).
Builder()
}
func (repo *UserRepo) PasswordCodeExists(ctx context.Context, userID string) (exists bool, err error) {
model := &passwordCodeCheck{
userID: userID,
}
err = repo.Eventstore.FilterToQueryReducer(ctx, model)
if err != nil {
return false, zerrors.ThrowPermissionDenied(err, "EVENT-SJ642", "Errors.Internal")
}
return model.exists, nil
}
type inviteCodeCheck struct {
userID string
exists bool
events int
}
func (p *inviteCodeCheck) Reduce() error {
p.exists = p.events > 0
return nil
}
func (p *inviteCodeCheck) AppendEvents(events ...eventstore.Event) {
p.events += len(events)
}
func (p *inviteCodeCheck) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(p.userID).
EventTypes(
user.HumanInviteCodeAddedType,
user.HumanInviteCodeSentType).
Builder()
}
func (repo *UserRepo) InviteCodeExists(ctx context.Context, userID string) (exists bool, err error) {
model := &inviteCodeCheck{
userID: userID,
}
err = repo.Eventstore.FilterToQueryReducer(ctx, model)
if err != nil {
return false, zerrors.ThrowPermissionDenied(err, "EVENT-GJ2os", "Errors.Internal")
}
return model.exists, nil
}

View File

@@ -0,0 +1,22 @@
package eventstore
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
usr_model "github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type UserSessionRepo struct {
View *view.View
}
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
userSessions, err := repo.View.UserSessionsByAgentID(ctx, authz.GetCtxData(ctx).AgentID, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
return model.UserSessionsToModel(userSessions), nil
}

View File

@@ -0,0 +1,118 @@
package handler
import (
"context"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5/pgconn"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/database"
"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"
)
type Config struct {
Client *database.DB
Eventstore *eventstore.Eventstore
BulkLimit uint64
FailureCountUntilSkip uint64
TransactionDuration time.Duration
Handlers map[string]*ConfigOverwrites
ActiveInstancer interface {
ActiveInstances() []string
}
}
type ConfigOverwrites struct {
MinimumCycleDuration time.Duration
}
var projections []*handler.Handler
func Register(ctx context.Context, configs Config, view *view.View, queries *query2.Queries) {
// make sure the slice does not contain old values
projections = nil
projections = append(projections, newUser(ctx,
configs.overwrite("User"),
view,
queries,
))
projections = append(projections, newUserSession(ctx,
configs.overwrite("UserSession"),
view,
queries,
id.SonyFlakeGenerator(),
))
projections = append(projections, newToken(ctx,
configs.overwrite("Token"),
view,
))
projections = append(projections, newRefreshToken(ctx,
configs.overwrite("RefreshToken"),
view,
))
}
func Start(ctx context.Context) {
for _, projection := range projections {
projection.Start(ctx)
}
}
func Projections() []*handler2.Handler {
return projections
}
func ProjectInstance(ctx context.Context) error {
for i, projection := range projections {
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID(), "index", fmt.Sprintf("%d/%d", i, len(projections))).Info("starting auth projection")
for {
_, err := projection.Trigger(ctx)
if err == nil {
break
}
var pgErr *pgconn.PgError
errors.As(err, &pgErr)
if pgErr.Code != database.PgUniqueConstraintErrorCode {
return err
}
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID()).WithError(err).Debug("auth projection failed because of unique constraint, retrying")
}
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID(), "index", fmt.Sprintf("%d/%d", i, len(projections))).Info("auth projection done")
}
return nil
}
func (config Config) overwrite(viewModel string) handler2.Config {
c := handler2.Config{
Client: config.Client,
Eventstore: config.Eventstore,
BulkLimit: uint16(config.BulkLimit),
RequeueEvery: 3 * time.Minute,
MaxFailureCount: uint8(config.FailureCountUntilSkip),
TransactionDuration: config.TransactionDuration,
ActiveInstancer: config.ActiveInstancer,
}
overwrite, ok := config.Handlers[viewModel]
if !ok {
return c
}
if overwrite.MinimumCycleDuration > 0 {
c.RequeueEvery = overwrite.MinimumCycleDuration
}
return c
}

View File

@@ -0,0 +1,179 @@
package handler
import (
"context"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
refreshTokenTable = "auth.refresh_tokens"
)
var _ handler.Projection = (*RefreshToken)(nil)
type RefreshToken struct {
view *auth_view.View
}
func newRefreshToken(
ctx context.Context,
config handler.Config,
view *auth_view.View,
) *handler.Handler {
return handler.NewHandler(
ctx,
&config,
&RefreshToken{
view: view,
},
)
}
// Name implements [handler.Projection]
func (*RefreshToken) Name() string {
return refreshTokenTable
}
// Reducers implements [handler.Projection]
func (t *RefreshToken) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: user.HumanRefreshTokenAddedType,
Reduce: t.Reduce,
},
{
Event: user.HumanRefreshTokenRenewedType,
Reduce: t.Reduce,
},
{
Event: user.HumanRefreshTokenRemovedType,
Reduce: t.Reduce,
},
{
Event: user.UserLockedType,
Reduce: t.Reduce,
},
{
Event: user.UserDeactivatedType,
Reduce: t.Reduce,
},
{
Event: user.UserRemovedType,
Reduce: t.Reduce,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: t.Reduce,
},
},
},
{
Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: org.OrgRemovedEventType,
Reduce: t.Reduce,
},
},
},
}
}
func (t *RefreshToken) 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.HumanRefreshTokenAddedType:
e, ok := event.(*user.HumanRefreshTokenAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-IoF6j", "reduce.wrong.event.type %s", user.HumanRefreshTokenAddedType)
}
columns := []handler.Column{
handler.NewCol(view_model.RefreshTokenKeyClientID, e.ClientID),
handler.NewCol(view_model.RefreshTokenKeyUserAgentID, e.UserAgentID),
handler.NewCol(view_model.RefreshTokenKeyUserID, e.Aggregate().ID),
handler.NewCol(view_model.RefreshTokenKeyInstanceID, e.Aggregate().InstanceID),
handler.NewCol(view_model.RefreshTokenKeyTokenID, e.TokenID),
handler.NewCol(view_model.RefreshTokenKeyResourceOwner, e.Aggregate().ResourceOwner),
handler.NewCol(view_model.RefreshTokenKeyCreationDate, event.CreatedAt()),
handler.NewCol(view_model.RefreshTokenKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.RefreshTokenKeySequence, event.Sequence()),
handler.NewCol(view_model.RefreshTokenKeyAMR, e.AuthMethodsReferences),
handler.NewCol(view_model.RefreshTokenKeyAuthTime, e.AuthTime),
handler.NewCol(view_model.RefreshTokenKeyAudience, e.Audience),
handler.NewCol(view_model.RefreshTokenKeyExpiration, event.CreatedAt().Add(e.Expiration)),
handler.NewCol(view_model.RefreshTokenKeyIdleExpiration, event.CreatedAt().Add(e.IdleExpiration)),
handler.NewCol(view_model.RefreshTokenKeyScopes, e.Scopes),
handler.NewCol(view_model.RefreshTokenKeyToken, e.TokenID),
handler.NewCol(view_model.RefreshTokenKeyActor, view_model.TokenActor{TokenActor: e.Actor}),
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.HumanRefreshTokenRenewedType:
e, ok := event.(*user.HumanRefreshTokenRenewedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AG43hq", "reduce.wrong.event.type %s", user.HumanRefreshTokenRenewedType)
}
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.RefreshTokenKeyIdleExpiration, event.CreatedAt().Add(e.IdleExpiration)),
handler.NewCol(view_model.RefreshTokenKeyToken, e.RefreshToken),
handler.NewCol(view_model.RefreshTokenKeyChangeDate, e.CreatedAt()),
handler.NewCol(view_model.RefreshTokenKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyTokenID, e.TokenID),
handler.NewCond(view_model.RefreshTokenKeyInstanceID, e.Aggregate().InstanceID),
},
), nil
case user.HumanRefreshTokenRemovedType:
e, ok := event.(*user.HumanRefreshTokenRemovedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SFF3t", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType)
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.RefreshTokenKeyTokenID, e.TokenID),
},
), nil
case user.UserLockedType,
user.UserDeactivatedType,
user.UserRemovedType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.RefreshTokenKeyUserID, event.Aggregate().ID),
},
), nil
case instance.InstanceRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
case org.OrgRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.RefreshTokenKeyResourceOwner, event.Aggregate().ResourceOwner),
},
), nil
default:
return handler.NewNoOpStatement(event), nil
}
}

View File

@@ -0,0 +1,361 @@
package handler
import (
"context"
"github.com/muhlemmer/gu"
"github.com/zitadel/logging"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
proj_model "github.com/zitadel/zitadel/internal/project/model"
project_es_model "github.com/zitadel/zitadel/internal/project/repository/eventsourcing/model"
proj_view "github.com/zitadel/zitadel/internal/project/repository/view"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/repository/user"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
tokenTable = "auth.tokens"
)
var _ handler.Projection = (*Token)(nil)
type Token struct {
view *auth_view.View
es handler.EventStore
}
func newToken(
ctx context.Context,
config handler.Config,
view *auth_view.View,
) *handler.Handler {
return handler.NewHandler(
ctx,
&config,
&Token{
view: view,
es: config.Eventstore,
},
)
}
// Name implements [handler.Projection]
func (*Token) Name() string {
return tokenTable
}
// Reducers implements [handler.Projection]
func (t *Token) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: user.PersonalAccessTokenAddedType,
Reduce: t.Reduce,
},
{
Event: user.UserTokenAddedType,
Reduce: t.Reduce,
},
{
Event: user.UserV1ProfileChangedType,
Reduce: t.Reduce,
},
{
Event: user.HumanProfileChangedType,
Reduce: t.Reduce,
},
{
Event: user.UserV1SignedOutType,
Reduce: t.Reduce,
},
{
Event: user.HumanSignedOutType,
Reduce: t.Reduce,
},
{
Event: user.UserLockedType,
Reduce: t.Reduce,
},
{
Event: user.UserDeactivatedType,
Reduce: t.Reduce,
},
{
Event: user.UserRemovedType,
Reduce: t.Reduce,
},
{
Event: user.UserTokenRemovedType,
Reduce: t.Reduce,
},
{
Event: user.PersonalAccessTokenRemovedType,
Reduce: t.Reduce,
},
{
Event: user.HumanRefreshTokenRemovedType,
Reduce: t.Reduce,
},
},
},
{
Aggregate: project.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: project.ApplicationDeactivatedType,
Reduce: t.Reduce,
},
{
Event: project.ApplicationRemovedType,
Reduce: t.Reduce,
},
{
Event: project.ProjectDeactivatedType,
Reduce: t.Reduce,
},
{
Event: project.ProjectRemovedType,
Reduce: t.Reduce,
},
},
},
{
Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: org.OrgRemovedEventType,
Reduce: t.Reduce,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: t.Reduce,
},
},
},
}
}
func (t *Token) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { //nolint:gocognit
// in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() {
case user.UserTokenAddedType:
e, ok := event.(*user.UserTokenAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-W4tnq", "reduce.wrong.event.type %s", user.UserTokenAddedType)
}
return handler.NewCreateStatement(event,
[]handler.Column{
handler.NewCol(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCol(view_model.TokenKeyUserID, event.Aggregate().ID),
handler.NewCol(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner),
handler.NewCol(view_model.TokenKeyID, e.TokenID),
handler.NewCol(view_model.TokenKeyCreationDate, event.CreatedAt()),
handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.TokenKeySequence, event.Sequence()),
handler.NewCol(view_model.TokenKeyApplicationID, e.ApplicationID),
handler.NewCol(view_model.TokenKeyUserAgentID, e.UserAgentID),
handler.NewCol(view_model.TokenKeyAudience, e.Audience),
handler.NewCol(view_model.TokenKeyScopes, e.Scopes),
handler.NewCol(view_model.TokenKeyExpiration, e.Expiration),
handler.NewCol(view_model.TokenKeyPreferredLanguage, e.PreferredLanguage),
handler.NewCol(view_model.TokenKeyRefreshTokenID, e.RefreshTokenID),
handler.NewCol(view_model.TokenKeyActor, view_model.TokenActor{TokenActor: e.Actor}),
handler.NewCol(view_model.TokenKeyIsPat, false),
},
), nil
case user.PersonalAccessTokenAddedType:
e, ok := event.(*user.PersonalAccessTokenAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-zF3rb", "reduce.wrong.event.type %s", user.PersonalAccessTokenAddedType)
}
return handler.NewCreateStatement(event,
[]handler.Column{
handler.NewCol(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCol(view_model.TokenKeyUserID, event.Aggregate().ID),
handler.NewCol(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner),
handler.NewCol(view_model.TokenKeyID, e.TokenID),
handler.NewCol(view_model.TokenKeyCreationDate, event.CreatedAt()),
handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.TokenKeySequence, event.Sequence()),
handler.NewCol(view_model.TokenKeyScopes, e.Scopes),
handler.NewCol(view_model.TokenKeyExpiration, e.Expiration),
handler.NewCol(view_model.TokenKeyIsPat, true),
},
), nil
case user.UserV1ProfileChangedType,
user.HumanProfileChangedType:
e, ok := event.(*user.HumanProfileChangedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-ASF2t", "reduce.wrong.event.type %s", user.HumanProfileChangedType)
}
if e.PreferredLanguage == nil {
return handler.NewNoOpStatement(event), nil
}
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.TokenKeyPreferredLanguage, gu.Value(e.PreferredLanguage).String()),
handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.TokenKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, e.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyUserID, e.Aggregate().ID),
},
), nil
case user.UserV1SignedOutType,
user.HumanSignedOutType:
e, ok := event.(*user.HumanSignedOutEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Wtn2q", "reduce.wrong.event.type %s", user.HumanSignedOutType)
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyUserID, event.Aggregate().ID),
handler.NewCond(view_model.TokenKeyUserAgentID, e.UserAgentID),
},
), nil
case user.UserLockedType,
user.UserDeactivatedType,
user.UserRemovedType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyUserID, event.Aggregate().ID),
},
), nil
case user.UserTokenRemovedType,
user.PersonalAccessTokenRemovedType:
var tokenID string
switch e := event.(type) {
case *user.UserTokenRemovedEvent:
tokenID = e.TokenID
case *user.PersonalAccessTokenRemovedEvent:
tokenID = e.TokenID
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SF3ga", "reduce.wrong.event.type %s", user.UserTokenRemovedType)
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyID, tokenID),
},
), nil
case user.HumanRefreshTokenRemovedType:
e, ok := event.(*user.HumanRefreshTokenRemovedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Sfe11", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType)
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyRefreshTokenID, e.TokenID),
},
), nil
case project.ApplicationDeactivatedType,
project.ApplicationRemovedType:
var applicationID string
switch e := event.(type) {
case *project.ApplicationDeactivatedEvent:
applicationID = e.AppID
case *project.ApplicationRemovedEvent:
applicationID = e.AppID
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SF3fq", "reduce.wrong.event.type %v", []eventstore.EventType{project.ApplicationDeactivatedType, project.ApplicationRemovedType})
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyApplicationID, applicationID),
},
), nil
case project.ProjectDeactivatedType,
project.ProjectRemovedType:
project, err := t.getProjectByID(context.Background(), event.Aggregate().ID, event.Aggregate().InstanceID)
if err != nil {
return nil, err
}
applicationIDs := make([]string, 0, len(project.Applications))
for _, app := range project.Applications {
if app.OIDCConfig != nil && app.OIDCConfig.ClientID != "" {
applicationIDs = append(applicationIDs, app.OIDCConfig.ClientID)
}
}
if len(applicationIDs) == 0 {
return handler.NewNoOpStatement(event), nil
}
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewOneOfTextCond(view_model.TokenKeyApplicationID, applicationIDs),
},
), nil
case instance.InstanceRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
case org.OrgRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner),
},
), nil
default:
return handler.NewNoOpStatement(event), nil
}
}
type userAgentIDPayload struct {
ID string `json:"userAgentID"`
}
func agentIDFromSession(event eventstore.Event) (string, error) {
payload := new(userAgentIDPayload)
if err := event.Unmarshal(payload); err != nil {
logging.WithError(err).Error("could not unmarshal event data")
return "", zerrors.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data")
}
return payload.ID, nil
}
func (t *Token) getProjectByID(ctx context.Context, projID, instanceID string) (*proj_model.Project, error) {
query, err := proj_view.ProjectByIDQuery(projID, instanceID, 0)
if err != nil {
return nil, err
}
esProject := &project_es_model.Project{
ObjectRoot: es_models.ObjectRoot{
AggregateID: projID,
},
}
events, err := t.es.Filter(ctx, query)
if err != nil {
return nil, err
}
if err = esProject.AppendEvents(events...); err != nil {
return nil, err
}
if esProject.Sequence == 0 {
return nil, zerrors.ThrowNotFound(nil, "EVENT-Dsdw2", "Errors.Project.NotFound")
}
return project_es_model.ProjectToModel(esProject), nil
}

View File

@@ -0,0 +1,306 @@
package handler
import (
"context"
"time"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
query2 "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
user_repo "github.com/zitadel/zitadel/internal/repository/user"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
userTable = "auth.users3"
)
type User struct {
view *auth_view.View
queries *query2.Queries
es handler.EventStore
}
var _ handler.Projection = (*User)(nil)
func newUser(
ctx context.Context,
config handler.Config,
view *auth_view.View,
queries *query2.Queries,
) *handler.Handler {
return handler.NewHandler(
ctx,
&config,
&User{
view: view,
queries: queries,
es: config.Eventstore,
},
)
}
func (*User) Name() string {
return userTable
}
func (u *User) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user_repo.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: user_repo.HumanOTPSMSRemovedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanOTPEmailRemovedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanAddedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1AddedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1RegisteredType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanRegisteredType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1PhoneRemovedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1MFAOTPVerifiedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1MFAInitSkippedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1PasswordChangedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanPhoneRemovedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanMFAOTPVerifiedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanU2FTokenVerifiedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanMFAInitSkippedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanPasswordChangedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanInitialCodeAddedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1InitialCodeAddedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserV1InitializedCheckSucceededType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanInitializedCheckSucceededType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanPasswordlessInitCodeAddedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanPasswordlessInitCodeRequestedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserRemovedType,
Reduce: u.ProcessUser,
},
},
},
{
Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: org.OrgRemovedEventType,
Reduce: u.ProcessOrg,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: u.ProcessInstance,
},
},
},
}
}
//nolint:gocognit
func (u *User) ProcessUser(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_repo.UserV1AddedType,
user_repo.HumanAddedType:
e, ok := event.(*user_repo.HumanAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SDAGF", "reduce.wrong.event.type %s", user_repo.HumanAddedType)
}
return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
case user_repo.UserV1RegisteredType,
user_repo.HumanRegisteredType:
e, ok := event.(*user_repo.HumanRegisteredEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AS1hz", "reduce.wrong.event.type %s", user_repo.HumanRegisteredType)
}
return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
case user_repo.UserV1PasswordChangedType,
user_repo.HumanPasswordChangedType:
e, ok := event.(*user_repo.HumanPasswordChangedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Gd31w", "reduce.wrong.event.type %s", user_repo.HumanPasswordChangedType)
}
return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
case user_repo.UserV1PhoneRemovedType,
user_repo.HumanPhoneRemovedType,
user_repo.UserV1MFAOTPVerifiedType,
user_repo.HumanMFAOTPVerifiedType,
user_repo.HumanOTPSMSRemovedType,
user_repo.HumanOTPEmailRemovedType,
user_repo.HumanU2FTokenVerifiedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserKeyMFAInitSkipped, time.Time{}),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
},
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
}), nil
case user_repo.UserV1MFAInitSkippedType,
user_repo.HumanMFAInitSkippedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserKeyMFAInitSkipped, event.CreatedAt()),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
},
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
}), nil
case user_repo.UserV1InitialCodeAddedType,
user_repo.HumanInitialCodeAddedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserKeyInitRequired, true),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
},
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
}), nil
case user_repo.UserV1InitializedCheckSucceededType,
user_repo.HumanInitializedCheckSucceededType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserKeyInitRequired, false),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
},
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
}), nil
case user_repo.HumanPasswordlessInitCodeAddedType,
user_repo.HumanPasswordlessInitCodeRequestedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserKeyPasswordlessInitRequired, true),
handler.NewCol(view_model.UserKeyPasswordInitRequired, false),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
},
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
handler.NewCond(view_model.UserKeyPasswordSet, false),
}), nil
case user_repo.UserRemovedType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
}), nil
default:
return handler.NewNoOpStatement(event), nil
}
}
func (u *User) setPasswordData(event eventstore.Event, secret *crypto.CryptoValue, hash string) *handler.Statement {
set := secret != nil || hash != ""
columns := []handler.Column{
handler.NewCol(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCol(view_model.UserKeyUserID, event.Aggregate().ID),
handler.NewCol(view_model.UserKeyResourceOwner, event.Aggregate().ResourceOwner),
handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserKeyPasswordSet, set),
handler.NewCol(view_model.UserKeyPasswordInitRequired, !set),
handler.NewCol(view_model.UserKeyPasswordChange, event.CreatedAt()),
}
return handler.NewUpsertStatement(event, columns[0:2], columns)
}
func (u *User) ProcessOrg(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 org.OrgRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserKeyResourceOwner, event.Aggregate().ID),
},
), nil
default:
return handler.NewNoOpStatement(event), nil
}
}
func (u *User) ProcessInstance(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 instance.InstanceRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
default:
return handler.NewNoOpStatement(event), nil
}
}

View File

@@ -0,0 +1,477 @@
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"
"github.com/zitadel/zitadel/internal/repository/user"
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
const (
userSessionTable = "auth.user_sessions"
IDPrefixV1 = "V1_"
)
type UserSession struct {
queries *query2.Queries
view *auth_view.View
es handler.EventStore
idGenerator id.Generator
}
var _ handler.Projection = (*UserSession)(nil)
func newUserSession(
ctx context.Context,
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,
idGenerator: idGenerator,
},
)
}
// Name implements [handler.Projection]
func (*UserSession) Name() string {
return userSessionTable
}
// Reducers implements [handler.Projection]
func (s *UserSession) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: user.UserV1PasswordCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.UserV1PasswordCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.UserV1MFAOTPCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.UserV1MFAOTPCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.UserV1SignedOutType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.UserIDPLoginCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.HumanMFAOTPCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.HumanMFAOTPCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.HumanU2FTokenCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.HumanU2FTokenCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordlessTokenCheckSucceededType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordlessTokenCheckFailedType,
Reduce: s.Reduce,
},
{
Event: user.HumanSignedOutType,
Reduce: s.Reduce,
},
{
Event: user.UserV1PasswordChangedType,
Reduce: s.Reduce,
},
{
Event: user.UserV1MFAOTPRemovedType,
Reduce: s.Reduce,
},
{
Event: user.UserLockedType,
Reduce: s.Reduce,
},
{
Event: user.UserDeactivatedType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordChangedType,
Reduce: s.Reduce,
},
{
Event: user.HumanMFAOTPRemovedType,
Reduce: s.Reduce,
},
{
Event: user.UserIDPLinkRemovedType,
Reduce: s.Reduce,
},
{
Event: user.UserIDPLinkCascadeRemovedType,
Reduce: s.Reduce,
},
{
Event: user.HumanPasswordlessTokenRemovedType,
Reduce: s.Reduce,
},
{
Event: user.HumanU2FTokenRemovedType,
Reduce: s.Reduce,
},
{
Event: user.UserRemovedType,
Reduce: s.Reduce,
},
{
Event: user.HumanRegisteredType,
Reduce: s.Reduce,
},
},
},
{
Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: org.OrgRemovedEventType,
Reduce: s.Reduce,
},
},
},
{
Aggregate: instance.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: instance.InstanceRemovedEventType,
Reduce: s.Reduce,
},
},
},
}
}
func (u *UserSession) sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
userAgent, err := agentIDFromSession(event)
if err != nil {
return nil, err
}
return append([]handler.Column{
handler.NewCol(view_model.UserSessionKeyUserAgentID, userAgent),
handler.NewCol(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.NewCol(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCol(view_model.UserSessionKeyCreationDate, handler.OnlySetValueOnInsert(userSessionTable, event.CreatedAt())),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyResourceOwner, event.Aggregate().ResourceOwner),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, 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 := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1PasswordCheckFailedType,
user.HumanPasswordCheckFailedType:
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1MFAOTPCheckSucceededType,
user.HumanMFAOTPCheckSucceededType:
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1MFAOTPCheckFailedType,
user.HumanMFAOTPCheckFailedType,
user.HumanU2FTokenCheckFailedType:
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1SignedOutType,
user.HumanSignedOutType:
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{}),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateTerminated),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserIDPLoginCheckSucceededType:
data := new(es_model.AuthRequest)
err := data.SetData(event)
if err != nil {
return nil, err
}
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.HumanU2FTokenCheckSucceededType:
data := new(es_model.AuthRequest)
err := data.SetData(event)
if err != nil {
return nil, err
}
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.HumanPasswordlessTokenCheckSucceededType:
data := new(es_model.AuthRequest)
err := data.SetData(event)
if err != nil {
return nil, err
}
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),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.HumanPasswordlessTokenCheckFailedType:
data := new(es_model.AuthRequest)
err := data.SetData(event)
if err != nil {
return nil, err
}
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
)
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserLockedType,
user.UserDeactivatedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateTerminated),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
},
), nil
case user.UserV1PasswordChangedType,
user.HumanPasswordChangedType:
userAgent, err := agentIDFromSession(event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(event,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent),
}),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent)),
handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
}),
), nil
case user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPRemovedType,
user.HumanU2FTokenRemovedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
},
), nil
case user.UserIDPLinkRemovedType,
user.UserIDPLinkCascadeRemovedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, ""),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond(view_model.UserSessionKeySelectedIDPConfigID, "")),
},
), nil
case user.HumanPasswordlessTokenRemovedType:
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
},
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
},
), nil
case user.UserRemovedType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
},
), nil
case user.HumanRegisteredType:
columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
)
if err != nil {
return nil, err
}
return handler.NewCreateStatement(event,
columns,
), nil
case instance.InstanceRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
case org.OrgRemovedEventType:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyResourceOwner, event.Aggregate().ResourceOwner),
},
), nil
default:
return handler.NewNoOpStatement(event), nil
}
}

View File

@@ -0,0 +1,139 @@
package eventsourcing
import (
"context"
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/eventstore"
auth_handler "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/handler"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/auth_request/repository/cache"
"github.com/zitadel/zitadel/internal/command"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
eventstore2 "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/query"
)
type Config struct {
SearchLimit uint64
Spooler auth_handler.Config
AmountOfCachedAuthRequests uint16
}
type EsRepository struct {
eventstore.UserRepo
eventstore.AuthRequestRepo
eventstore.TokenRepo
eventstore.RefreshTokenRepo
eventstore.UserSessionRepo
eventstore.OrgRepository
}
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *database.DB, esV2 *eventstore2.Eventstore, oidcEncryption crypto.EncryptionAlgorithm, userEncryption crypto.EncryptionAlgorithm) (*EsRepository, error) {
view, err := auth_view.StartView(dbClient, oidcEncryption, queries, esV2)
if err != nil {
return nil, err
}
auth_handler.Register(ctx, conf.Spooler, view, queries)
auth_handler.Start(ctx)
authReq := cache.Start(dbClient, conf.AmountOfCachedAuthRequests)
userRepo := eventstore.UserRepo{
SearchLimit: conf.SearchLimit,
Eventstore: esV2,
View: view,
Query: queries,
SystemDefaults: systemDefaults,
}
//TODO: remove as soon as possible
queryView := queryViewWrapper{
queries,
view,
}
return &EsRepository{
userRepo,
eventstore.AuthRequestRepo{
PrivacyPolicyProvider: queries,
LabelPolicyProvider: queries,
Command: command,
Query: queries,
OrgViewProvider: queries,
AuthRequests: authReq,
View: view,
UserCodeAlg: userEncryption,
UserSessionViewProvider: view,
UserViewProvider: view,
UserCommandProvider: command,
UserEventProvider: &userRepo,
IDPProviderViewProvider: queries,
IDPUserLinksProvider: queries,
LockoutPolicyViewProvider: queries,
LoginPolicyViewProvider: queries,
PasswordAgePolicyProvider: queries,
UserGrantProvider: queryView,
ProjectProvider: queryView,
ApplicationProvider: queries,
CustomTextProvider: queries,
PasswordReset: command,
PasswordChecker: command,
IdGenerator: id.SonyFlakeGenerator(),
},
eventstore.TokenRepo{
View: view,
Eventstore: esV2,
},
eventstore.RefreshTokenRepo{
View: view,
Eventstore: esV2,
SearchLimit: conf.SearchLimit,
KeyAlgorithm: oidcEncryption,
},
eventstore.UserSessionRepo{
View: view,
},
eventstore.OrgRepository{
SearchLimit: conf.SearchLimit,
View: view,
SystemDefaults: systemDefaults,
Eventstore: esV2,
Query: queries,
},
}, nil
}
type queryViewWrapper struct {
*query.Queries
*auth_view.View
}
func (q queryViewWrapper) UserGrantsByProjectAndUserID(ctx context.Context, projectID, userID string) ([]*query.UserGrant, error) {
userGrantProjectID, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
}
userGrantUserID, err := query.NewUserGrantUserIDSearchQuery(userID)
if err != nil {
return nil, err
}
activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
if err != nil {
return nil, err
}
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID, activeQuery}}
grants, err := q.Queries.UserGrants(ctx, queries, true, nil)
if err != nil {
return nil, err
}
return grants.UserGrants, nil
}
func (repo *EsRepository) Health(ctx context.Context) error {
if err := repo.UserRepo.Health(ctx); err != nil {
return err
}
return repo.AuthRequestRepo.Health(ctx)
}

View File

@@ -0,0 +1,46 @@
package view
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/query"
user_model "github.com/zitadel/zitadel/internal/user/model"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
)
const (
refreshTokenTable = "auth.refresh_tokens"
)
func (v *View) RefreshTokenByID(tokenID, instanceID string) (*model.RefreshTokenView, error) {
return usr_view.RefreshTokenByID(v.Db, refreshTokenTable, tokenID, instanceID)
}
func (v *View) RefreshTokensByUserID(userID, instanceID string) ([]*model.RefreshTokenView, error) {
return usr_view.RefreshTokensByUserID(v.Db, refreshTokenTable, userID, instanceID)
}
func (v *View) SearchRefreshTokens(request *user_model.RefreshTokenSearchRequest) ([]*model.RefreshTokenView, uint64, error) {
return usr_view.SearchRefreshTokens(v.Db, refreshTokenTable, request)
}
func (v *View) GetLatestRefreshTokenSequence(ctx context.Context) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2),
}
q.Queries[0], err = query.NewCurrentStatesInstanceIDSearchQuery(authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
q.Queries[1], err = query.NewCurrentStatesProjectionSearchQuery(refreshTokenTable)
if err != nil {
return nil, err
}
states, err := v.query.SearchCurrentStates(ctx, q)
if err != nil || states.SearchResponse.Count == 0 {
return nil, err
}
return states.CurrentStates[0], nil
}

View File

@@ -0,0 +1,44 @@
package view
import (
"context"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
)
const (
tokenTable = "auth.tokens"
)
func (v *View) TokenByIDs(tokenID, userID, instanceID string) (*model.TokenView, error) {
return usr_view.TokenByIDs(v.Db, tokenTable, tokenID, userID, instanceID)
}
func (v *View) TokensByUserID(userID, instanceID string) ([]*model.TokenView, error) {
return usr_view.TokensByUserID(v.Db, tokenTable, userID, instanceID)
}
func (v *View) GetLatestTokenSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2),
}
q.Queries[0], err = query.NewCurrentStatesInstanceIDSearchQuery(instanceID)
if err != nil {
return nil, err
}
q.Queries[1], err = query.NewCurrentStatesProjectionSearchQuery(tokenTable)
if err != nil {
return nil, err
}
states, err := v.query.SearchCurrentStates(ctx, q)
if err != nil || states.SearchResponse.Count == 0 {
return nil, err
}
return states.CurrentStates[0], nil
}

View File

@@ -0,0 +1,161 @@
package view
import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/query"
usr_model "github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
userTable = "auth.users3"
)
func (v *View) UserByID(ctx context.Context, userID, instanceID string) (*model.UserView, error) {
return view.UserByID(ctx, v.Db, userID, instanceID)
}
func (v *View) UserByLoginName(ctx context.Context, loginName, instanceID string) (*model.UserView, error) {
queriedUser, err := v.query.GetNotifyUserByLoginName(ctx, true, loginName)
if err != nil {
return nil, err
}
//nolint: contextcheck // no lint was added because refactor would change too much code
return view.UserByID(ctx, v.Db, queriedUser.ID, instanceID)
}
func (v *View) UserByLoginNameAndResourceOwner(ctx context.Context, loginName, resourceOwner, instanceID string) (*model.UserView, error) {
queriedUser, err := v.query.GetNotifyUserByLoginName(ctx, true, loginName)
if err != nil {
return nil, err
}
//nolint: contextcheck // no lint was added because refactor would change too much code
user, err := view.UserByID(ctx, v.Db, queriedUser.ID, instanceID)
if err != nil {
return nil, err
}
if user.ResourceOwner != resourceOwner {
return nil, zerrors.ThrowNotFound(nil, "VIEW-qScmi", "Errors.User.NotFound")
}
return user, nil
}
func (v *View) UserByEmail(ctx context.Context, email, instanceID string) (*model.UserView, error) {
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(email)
if err != nil {
return nil, err
}
return v.userByID(ctx, instanceID, emailQuery)
}
func (v *View) UserByEmailAndResourceOwner(ctx context.Context, email, resourceOwner, instanceID string) (*model.UserView, error) {
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(email)
if err != nil {
return nil, err
}
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(ctx, instanceID, emailQuery, resourceOwnerQuery)
}
func (v *View) UserByPhone(ctx context.Context, phone, instanceID string) (*model.UserView, error) {
phoneQuery, err := query.NewUserVerifiedPhoneSearchQuery(phone, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(ctx, instanceID, phoneQuery)
}
func (v *View) UserByPhoneAndResourceOwner(ctx context.Context, phone, resourceOwner, instanceID string) (*model.UserView, error) {
phoneQuery, err := query.NewUserVerifiedPhoneSearchQuery(phone, query.TextEquals)
if err != nil {
return nil, err
}
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
if err != nil {
return nil, err
}
return v.userByID(ctx, instanceID, phoneQuery, resourceOwnerQuery)
}
func (v *View) userByID(ctx context.Context, instanceID string, queries ...query.SearchQuery) (*model.UserView, error) {
queriedUser, err := v.query.GetNotifyUser(ctx, true, queries...)
if err != nil {
return nil, err
}
// always load the latest sequence first, so in case the user was not found by id,
// the sequence will be equal or lower than the actual projection and no events are lost
sequence, err := v.GetLatestUserSequence(ctx, instanceID)
logging.WithFields("instanceID", instanceID).
OnError(err).
Errorf("could not get current sequence for userByID")
user, err := view.UserByID(ctx, v.Db, queriedUser.ID, instanceID)
if err != nil && !zerrors.IsNotFound(err) {
return nil, err
}
if err != nil {
user = new(model.UserView)
if sequence != nil {
user.ChangeDate = sequence.EventCreatedAt
}
}
query, err := view.UserByIDQuery(queriedUser.ID, instanceID, user.ChangeDate, user.EventTypes())
if err != nil {
return nil, err
}
events, err := v.es.Filter(ctx, query)
if err != nil && user.Sequence == 0 {
return nil, err
} else if err != nil {
return user, nil
}
userCopy := *user
for _, event := range events {
if err := user.AppendEvent(event); err != nil {
return &userCopy, nil
}
}
if user.State == int32(usr_model.UserStateDeleted) {
return nil, zerrors.ThrowNotFound(nil, "VIEW-r4y8r", "Errors.User.NotFound")
}
return user, nil
}
func (v *View) GetLatestUserSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2),
}
q.Queries[0], err = query.NewCurrentStatesInstanceIDSearchQuery(instanceID)
if err != nil {
return nil, err
}
q.Queries[1], err = query.NewCurrentStatesProjectionSearchQuery(userTable)
if err != nil {
return nil, err
}
states, err := v.query.SearchCurrentStates(ctx, q)
if err != nil || states.SearchResponse.Count == 0 {
return nil, err
}
return states.CurrentStates[0], nil
}

View File

@@ -0,0 +1,52 @@
package view
import (
"context"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
)
const (
userSessionTable = "auth.user_sessions"
)
func (v *View) UserSessionByIDs(ctx context.Context, agentID, userID, instanceID string) (*model.UserSessionView, error) {
return view.UserSessionByIDs(ctx, v.client, agentID, userID, instanceID)
}
func (v *View) UserSessionByID(ctx context.Context, userSessionID, instanceID string) (*model.UserSessionView, error) {
return view.UserSessionByID(ctx, v.client, userSessionID, instanceID)
}
func (v *View) UserSessionsByAgentID(ctx context.Context, agentID, instanceID string) ([]*model.UserSessionView, error) {
return view.UserSessionsByAgentID(ctx, v.client, agentID, instanceID)
}
func (v *View) UserAgentIDBySessionID(ctx context.Context, sessionID, instanceID string) (string, error) {
return view.UserAgentIDBySessionID(ctx, v.client, sessionID, instanceID)
}
func (v *View) ActiveUserSessionsBySessionID(ctx context.Context, sessionID, instanceID string) (userAgentID string, sessions map[string]string, err error) {
return view.ActiveUserSessionsBySessionID(ctx, v.client, sessionID, instanceID)
}
func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2),
}
q.Queries[0], err = query.NewCurrentStatesInstanceIDSearchQuery(instanceID)
if err != nil {
return nil, err
}
q.Queries[1], err = query.NewCurrentStatesProjectionSearchQuery(userSessionTable)
if err != nil {
return nil, err
}
states, err := v.query.SearchCurrentStates(ctx, q)
if err != nil || states.SearchResponse.Count == 0 {
return nil, err
}
return states.CurrentStates[0], nil
}

View File

@@ -0,0 +1,36 @@
package view
import (
"github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query"
)
type View struct {
Db *gorm.DB
client *database.DB
keyAlgorithm crypto.EncryptionAlgorithm
query *query.Queries
es *eventstore.Eventstore
}
func StartView(sqlClient *database.DB, keyAlgorithm crypto.EncryptionAlgorithm, queries *query.Queries, es *eventstore.Eventstore) (*View, error) {
gorm, err := gorm.Open("postgres", sqlClient.DB)
if err != nil {
return nil, err
}
return &View{
Db: gorm,
client: sqlClient,
keyAlgorithm: keyAlgorithm,
query: queries,
es: es,
}, nil
}
func (v *View) Health() (err error) {
return v.Db.DB().Ping()
}

View File

@@ -0,0 +1,13 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
)
type OrgRepository interface {
GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
GetLoginText(ctx context.Context, orgID string) ([]*domain.CustomText, error)
}

View File

@@ -0,0 +1,13 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/internal/user/model"
)
type RefreshTokenRepository interface {
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)
}

View File

@@ -0,0 +1,15 @@
package repository
import (
"context"
)
type Repository interface {
Health(context.Context) error
UserRepository
AuthRequestRepository
TokenRepository
UserSessionRepository
OrgRepository
RefreshTokenRepository
}

View File

@@ -0,0 +1,11 @@
package repository
import (
"context"
usr_model "github.com/zitadel/zitadel/internal/user/model"
)
type TokenRepository interface {
TokenByIDs(ctx context.Context, userID, tokenID string) (*usr_model.TokenView, error)
}

View File

@@ -0,0 +1,15 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type UserRepository interface {
UserSessionsByAgentID(ctx context.Context, agentID string) (sessions []command.HumanSignOutSession, err error)
UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error)
UserSessionByID(ctx context.Context, sessionID string) (*model.UserSessionView, error)
ActiveUserSessionsBySessionID(ctx context.Context, sessionID string) (userAgentID string, sessions []command.HumanSignOutSession, err error)
}

View File

@@ -0,0 +1,11 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/internal/user/model"
)
type UserSessionRepository interface {
GetMyUserSessions(ctx context.Context) ([]*model.UserSessionView, error)
}