mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:07:31 +00:00
chore: move the go code into a subfolder
This commit is contained in:
45
apps/api/internal/auth/repository/auth_request.go
Normal file
45
apps/api/internal/auth/repository/auth_request.go
Normal 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
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
361
apps/api/internal/auth/repository/eventsourcing/handler/token.go
Normal file
361
apps/api/internal/auth/repository/eventsourcing/handler/token.go
Normal 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
|
||||
}
|
306
apps/api/internal/auth/repository/eventsourcing/handler/user.go
Normal file
306
apps/api/internal/auth/repository/eventsourcing/handler/user.go
Normal 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
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
139
apps/api/internal/auth/repository/eventsourcing/repository.go
Normal file
139
apps/api/internal/auth/repository/eventsourcing/repository.go
Normal 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)
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
161
apps/api/internal/auth/repository/eventsourcing/view/user.go
Normal file
161
apps/api/internal/auth/repository/eventsourcing/view/user.go
Normal 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
|
||||
}
|
@@ -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
|
||||
}
|
36
apps/api/internal/auth/repository/eventsourcing/view/view.go
Normal file
36
apps/api/internal/auth/repository/eventsourcing/view/view.go
Normal 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()
|
||||
}
|
13
apps/api/internal/auth/repository/org.go
Normal file
13
apps/api/internal/auth/repository/org.go
Normal 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)
|
||||
}
|
13
apps/api/internal/auth/repository/refresh_token.go
Normal file
13
apps/api/internal/auth/repository/refresh_token.go
Normal 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)
|
||||
}
|
15
apps/api/internal/auth/repository/repository.go
Normal file
15
apps/api/internal/auth/repository/repository.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Health(context.Context) error
|
||||
UserRepository
|
||||
AuthRequestRepository
|
||||
TokenRepository
|
||||
UserSessionRepository
|
||||
OrgRepository
|
||||
RefreshTokenRepository
|
||||
}
|
11
apps/api/internal/auth/repository/token.go
Normal file
11
apps/api/internal/auth/repository/token.go
Normal 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)
|
||||
}
|
15
apps/api/internal/auth/repository/user.go
Normal file
15
apps/api/internal/auth/repository/user.go
Normal 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)
|
||||
}
|
11
apps/api/internal/auth/repository/user_session.go
Normal file
11
apps/api/internal/auth/repository/user_session.go
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user