mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +00:00
perf(oidc): optimize the introspection endpoint (#6909)
* get key by id and cache them
* userinfo from events for v2 tokens
* improve keyset caching
* concurrent token and client checks
* client and project in single query
* logging and otel
* drop owner_removed column on apps and authN tables
* userinfo and project roles in go routines
* get oidc user info from projections and add actions
* add avatar URL
* some cleanup
* pull oidc work branch
* remove storage from server
* add config flag for experimental introspection
* legacy introspection flag
* drop owner_removed column on user projections
* drop owner_removed column on useer_metadata
* query userinfo unit test
* query introspection client test
* add user_grants to the userinfo query
* handle PAT scopes
* bring triggers back
* test instance keys query
* add userinfo unit tests
* unit test keys
* go mod tidy
* solve some bugs
* fix missing preferred login name
* do not run triggers in go routines, they seem to deadlock
* initialize the trigger handlers late with a sync.OnceValue
* Revert "do not run triggers in go routines, they seem to deadlock"
This reverts commit 2a03da2127
.
* add missing translations
* chore: update go version for linting
* pin oidc version
* parse a global time location for query test
* fix linter complains
* upgrade go lint
* fix more linting issues
---------
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
@@ -5,11 +5,9 @@ import (
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/zitadel/logging"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@@ -17,7 +15,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
@@ -28,32 +25,32 @@ type Users struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
State domain.UserState
|
||||
Type domain.UserType
|
||||
Username string
|
||||
LoginNames database.TextArray[string]
|
||||
PreferredLoginName string
|
||||
Human *Human
|
||||
Machine *Machine
|
||||
ID string `json:"id,omitempty"`
|
||||
CreationDate time.Time `json:"creation_date,omitempty"`
|
||||
ChangeDate time.Time `json:"change_date,omitempty"`
|
||||
ResourceOwner string `json:"resource_owner,omitempty"`
|
||||
Sequence uint64 `json:"sequence,omitempty"`
|
||||
State domain.UserState `json:"state,omitempty"`
|
||||
Type domain.UserType `json:"type,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
LoginNames database.TextArray[string] `json:"login_names,omitempty"`
|
||||
PreferredLoginName string `json:"preferred_login_name,omitempty"`
|
||||
Human *Human `json:"human,omitempty"`
|
||||
Machine *Machine `json:"machine,omitempty"`
|
||||
}
|
||||
|
||||
type Human struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
AvatarKey string
|
||||
PreferredLanguage language.Tag
|
||||
Gender domain.Gender
|
||||
Email domain.EmailAddress
|
||||
IsEmailVerified bool
|
||||
Phone domain.PhoneNumber
|
||||
IsPhoneVerified bool
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
NickName string `json:"nick_name,omitempty"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
AvatarKey string `json:"avatar_key,omitempty"`
|
||||
PreferredLanguage language.Tag `json:"preferred_language,omitempty"`
|
||||
Gender domain.Gender `json:"gender,omitempty"`
|
||||
Email domain.EmailAddress `json:"email,omitempty"`
|
||||
IsEmailVerified bool `json:"is_email_verified,omitempty"`
|
||||
Phone domain.PhoneNumber `json:"phone,omitempty"`
|
||||
IsPhoneVerified bool `json:"is_phone_verified,omitempty"`
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
@@ -92,10 +89,10 @@ type Phone struct {
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
Name string
|
||||
Description string
|
||||
HasSecret bool
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
HasSecret bool `json:"has_secret,omitempty"`
|
||||
AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"`
|
||||
}
|
||||
|
||||
type NotifyUser struct {
|
||||
@@ -170,10 +167,6 @@ var (
|
||||
name: projection.UserTypeCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserOwnerRemovedCol = Column{
|
||||
name: projection.UserOwnerRemovedCol,
|
||||
table: userTable,
|
||||
}
|
||||
|
||||
userLoginNamesTable = loginNameTable.setAlias("login_names")
|
||||
userLoginNamesUserIDCol = LoginNameUserIDCol.setTable(userLoginNamesTable)
|
||||
@@ -320,11 +313,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func addUserWithoutOwnerRemoved(eq map[string]interface{}) {
|
||||
eq[UserOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, withOwnerRemoved bool, queries ...SearchQuery) (user *User, err error) {
|
||||
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, queries ...SearchQuery) (user *User, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -340,9 +329,6 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userI
|
||||
UserIDCol.identifier(): userID,
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
addUserWithoutOwnerRemoved(eq)
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-FBg21", "Errors.Query.SQLStatment")
|
||||
@@ -355,7 +341,7 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userI
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwnerRemoved bool, queries ...SearchQuery) (user *User, err error) {
|
||||
func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (user *User, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -370,9 +356,6 @@ func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwner
|
||||
eq := sq.Eq{
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
addUserWithoutOwnerRemoved(eq)
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Dnhr2", "Errors.Query.SQLStatment")
|
||||
@@ -385,7 +368,7 @@ func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwner
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (profile *Profile, err error) {
|
||||
func (q *Queries) GetHumanProfile(ctx context.Context, userID string, queries ...SearchQuery) (profile *Profile, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -397,9 +380,6 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerR
|
||||
UserIDCol.identifier(): userID,
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
eq[UserOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment")
|
||||
@@ -412,7 +392,7 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerR
|
||||
return profile, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (email *Email, err error) {
|
||||
func (q *Queries) GetHumanEmail(ctx context.Context, userID string, queries ...SearchQuery) (email *Email, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -424,9 +404,6 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRem
|
||||
UserIDCol.identifier(): userID,
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
eq[UserOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatment")
|
||||
@@ -439,7 +416,7 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRem
|
||||
return email, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (phone *Phone, err error) {
|
||||
func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...SearchQuery) (phone *Phone, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -451,9 +428,6 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRem
|
||||
UserIDCol.identifier(): userID,
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
eq[UserOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
|
||||
@@ -466,7 +440,7 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRem
|
||||
return phone, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, withOwnerRemoved bool, queries ...SearchQuery) (user *NotifyUser, err error) {
|
||||
func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (user *NotifyUser, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -482,9 +456,6 @@ func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, u
|
||||
UserIDCol.identifier(): userID,
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
addUserWithoutOwnerRemoved(eq)
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
|
||||
@@ -497,7 +468,7 @@ func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, u
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withOwnerRemoved bool, queries ...SearchQuery) (user *NotifyUser, err error) {
|
||||
func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, queries ...SearchQuery) (user *NotifyUser, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -512,9 +483,6 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withO
|
||||
eq := sq.Eq{
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
addUserWithoutOwnerRemoved(eq)
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
|
||||
@@ -527,15 +495,12 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withO
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries, withOwnerRemoved bool) (users *Users, err error) {
|
||||
func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (users *Users, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareUsersQuery(ctx, q.client)
|
||||
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
if !withOwnerRemoved {
|
||||
addUserWithoutOwnerRemoved(eq)
|
||||
}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
@@ -554,7 +519,7 @@ func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries, w
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwner string, withOwnerRemoved bool) (isUnique bool, err error) {
|
||||
func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwner string) (isUnique bool, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -585,9 +550,6 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn
|
||||
query = q.toQuery(query)
|
||||
}
|
||||
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
if !withOwnerRemoved {
|
||||
eq[UserOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return false, errors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
|
||||
@@ -715,23 +677,7 @@ func NewUserLoginNameExistsQuery(value string, comparison TextComparison) (Searc
|
||||
}
|
||||
|
||||
func triggerUserProjections(ctx context.Context) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
func() {
|
||||
_, traceSpan := tracing.NewNamedSpan(ctx, "TriggerUserProjection")
|
||||
_, err := projection.UserProjection.Trigger(ctx, handler.WithAwaitRunning())
|
||||
logging.OnError(err).Debug("trigger failed")
|
||||
traceSpan.EndWithError(err)
|
||||
wg.Done()
|
||||
}()
|
||||
func() {
|
||||
_, traceSpan := tracing.NewNamedSpan(ctx, "TriggerLoginNameProjection")
|
||||
_, err := projection.LoginNameProjection.Trigger(ctx, handler.WithAwaitRunning())
|
||||
traceSpan.EndWithError(err)
|
||||
logging.OnError(err).Debug("trigger failed")
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
triggerBatch(ctx, projection.UserProjection, projection.LoginNameProjection)
|
||||
}
|
||||
|
||||
func prepareLoginNamesQuery() (string, []interface{}, error) {
|
||||
|
Reference in New Issue
Block a user