2023-05-05 15:34:53 +00:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
errs "errors"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
sq "github.com/Masterminds/squirrel"
|
|
|
|
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/call"
|
|
|
|
"github.com/zitadel/zitadel/internal/database"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
|
|
"github.com/zitadel/zitadel/internal/query/projection"
|
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Sessions struct {
|
|
|
|
SearchResponse
|
|
|
|
Sessions []*Session
|
|
|
|
}
|
|
|
|
|
|
|
|
type Session struct {
|
|
|
|
ID string
|
|
|
|
CreationDate time.Time
|
|
|
|
ChangeDate time.Time
|
|
|
|
Sequence uint64
|
|
|
|
State domain.SessionState
|
|
|
|
ResourceOwner string
|
2023-06-27 12:36:07 +00:00
|
|
|
Domain string
|
2023-05-05 15:34:53 +00:00
|
|
|
Creator string
|
|
|
|
UserFactor SessionUserFactor
|
|
|
|
PasswordFactor SessionPasswordFactor
|
2023-06-21 14:06:18 +00:00
|
|
|
IntentFactor SessionIntentFactor
|
2023-06-07 15:28:42 +00:00
|
|
|
PasskeyFactor SessionPasskeyFactor
|
2023-05-05 15:34:53 +00:00
|
|
|
Metadata map[string][]byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type SessionUserFactor struct {
|
|
|
|
UserID string
|
2023-07-14 11:16:16 +00:00
|
|
|
ResourceOwner string
|
2023-05-05 15:34:53 +00:00
|
|
|
UserCheckedAt time.Time
|
|
|
|
LoginName string
|
|
|
|
DisplayName string
|
|
|
|
}
|
|
|
|
|
|
|
|
type SessionPasswordFactor struct {
|
|
|
|
PasswordCheckedAt time.Time
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:06:18 +00:00
|
|
|
type SessionIntentFactor struct {
|
|
|
|
IntentCheckedAt time.Time
|
|
|
|
}
|
|
|
|
|
2023-06-07 15:28:42 +00:00
|
|
|
type SessionPasskeyFactor struct {
|
|
|
|
PasskeyCheckedAt time.Time
|
|
|
|
}
|
|
|
|
|
2023-05-05 15:34:53 +00:00
|
|
|
type SessionsSearchQueries struct {
|
|
|
|
SearchRequest
|
|
|
|
Queries []SearchQuery
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *SessionsSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
|
|
|
query = q.SearchRequest.toQuery(query)
|
|
|
|
for _, q := range q.Queries {
|
|
|
|
query = q.toQuery(query)
|
|
|
|
}
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
sessionsTable = table{
|
|
|
|
name: projection.SessionsProjectionTable,
|
|
|
|
instanceIDCol: projection.SessionColumnInstanceID,
|
|
|
|
}
|
|
|
|
SessionColumnID = Column{
|
|
|
|
name: projection.SessionColumnID,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnCreationDate = Column{
|
|
|
|
name: projection.SessionColumnCreationDate,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnChangeDate = Column{
|
|
|
|
name: projection.SessionColumnChangeDate,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnSequence = Column{
|
|
|
|
name: projection.SessionColumnSequence,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnState = Column{
|
|
|
|
name: projection.SessionColumnState,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnResourceOwner = Column{
|
|
|
|
name: projection.SessionColumnResourceOwner,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
2023-06-27 12:36:07 +00:00
|
|
|
SessionColumnDomain = Column{
|
|
|
|
name: projection.SessionColumnDomain,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnInstanceID = Column{
|
|
|
|
name: projection.SessionColumnInstanceID,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnCreator = Column{
|
|
|
|
name: projection.SessionColumnCreator,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnUserID = Column{
|
|
|
|
name: projection.SessionColumnUserID,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnUserCheckedAt = Column{
|
|
|
|
name: projection.SessionColumnUserCheckedAt,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnPasswordCheckedAt = Column{
|
|
|
|
name: projection.SessionColumnPasswordCheckedAt,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
2023-06-21 14:06:18 +00:00
|
|
|
SessionColumnIntentCheckedAt = Column{
|
|
|
|
name: projection.SessionColumnIntentCheckedAt,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
2023-06-07 15:28:42 +00:00
|
|
|
SessionColumnPasskeyCheckedAt = Column{
|
|
|
|
name: projection.SessionColumnPasskeyCheckedAt,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnMetadata = Column{
|
|
|
|
name: projection.SessionColumnMetadata,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
SessionColumnToken = Column{
|
|
|
|
name: projection.SessionColumnTokenID,
|
|
|
|
table: sessionsTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-07-07 08:15:05 +00:00
|
|
|
func (q *Queries) SessionByID(ctx context.Context, shouldTriggerBulk bool, id, sessionToken string) (_ *Session, err error) {
|
2023-05-05 15:34:53 +00:00
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
2023-07-07 08:15:05 +00:00
|
|
|
if shouldTriggerBulk {
|
|
|
|
ctx = projection.SessionProjection.Trigger(ctx)
|
|
|
|
}
|
|
|
|
|
2023-05-05 15:34:53 +00:00
|
|
|
query, scan := prepareSessionQuery(ctx, q.client)
|
|
|
|
stmt, args, err := query.Where(
|
|
|
|
sq.Eq{
|
|
|
|
SessionColumnID.identifier(): id,
|
|
|
|
SessionColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
|
|
|
},
|
|
|
|
).ToSql()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.ThrowInternal(err, "QUERY-dn9JW", "Errors.Query.SQLStatement")
|
|
|
|
}
|
|
|
|
|
|
|
|
row := q.client.QueryRowContext(ctx, stmt, args...)
|
|
|
|
session, tokenID, err := scan(row)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if sessionToken == "" {
|
|
|
|
return session, nil
|
|
|
|
}
|
|
|
|
if err := q.sessionTokenVerifier(ctx, sessionToken, session.ID, tokenID); err != nil {
|
|
|
|
return nil, errors.ThrowPermissionDenied(nil, "QUERY-dsfr3", "Errors.PermissionDenied")
|
|
|
|
}
|
|
|
|
return session, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *Queries) SearchSessions(ctx context.Context, queries *SessionsSearchQueries) (_ *Sessions, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
query, scan := prepareSessionsQuery(ctx, q.client)
|
|
|
|
stmt, args, err := queries.toQuery(query).
|
|
|
|
Where(sq.Eq{
|
|
|
|
SessionColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
|
|
|
}).ToSql()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.ThrowInvalidArgument(err, "QUERY-sn9Jf", "Errors.Query.InvalidRequest")
|
|
|
|
}
|
|
|
|
|
|
|
|
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
|
|
|
if err != nil || rows.Err() != nil {
|
|
|
|
return nil, errors.ThrowInternal(err, "QUERY-Sfg42", "Errors.Internal")
|
|
|
|
}
|
|
|
|
sessions, err := scan(rows)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sessions.LatestSequence, err = q.latestSequence(ctx, sessionsTable)
|
|
|
|
return sessions, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewSessionIDsSearchQuery(ids []string) (SearchQuery, error) {
|
|
|
|
list := make([]interface{}, len(ids))
|
|
|
|
for i, value := range ids {
|
|
|
|
list[i] = value
|
|
|
|
}
|
|
|
|
return NewListQuery(SessionColumnID, list, ListIn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewSessionCreatorSearchQuery(creator string) (SearchQuery, error) {
|
|
|
|
return NewTextQuery(SessionColumnCreator, creator, TextEquals)
|
|
|
|
}
|
|
|
|
|
|
|
|
func prepareSessionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*Session, string, error)) {
|
|
|
|
return sq.Select(
|
|
|
|
SessionColumnID.identifier(),
|
|
|
|
SessionColumnCreationDate.identifier(),
|
|
|
|
SessionColumnChangeDate.identifier(),
|
|
|
|
SessionColumnSequence.identifier(),
|
|
|
|
SessionColumnState.identifier(),
|
|
|
|
SessionColumnResourceOwner.identifier(),
|
|
|
|
SessionColumnCreator.identifier(),
|
2023-06-27 12:36:07 +00:00
|
|
|
SessionColumnDomain.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnUserID.identifier(),
|
|
|
|
SessionColumnUserCheckedAt.identifier(),
|
|
|
|
LoginNameNameCol.identifier(),
|
|
|
|
HumanDisplayNameCol.identifier(),
|
2023-07-14 11:16:16 +00:00
|
|
|
UserResourceOwnerCol.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnPasswordCheckedAt.identifier(),
|
2023-06-21 14:06:18 +00:00
|
|
|
SessionColumnIntentCheckedAt.identifier(),
|
2023-06-07 15:28:42 +00:00
|
|
|
SessionColumnPasskeyCheckedAt.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnMetadata.identifier(),
|
|
|
|
SessionColumnToken.identifier(),
|
|
|
|
).From(sessionsTable.identifier()).
|
|
|
|
LeftJoin(join(LoginNameUserIDCol, SessionColumnUserID)).
|
2023-07-14 11:16:16 +00:00
|
|
|
LeftJoin(join(HumanUserIDCol, SessionColumnUserID)).
|
|
|
|
LeftJoin(join(UserIDCol, SessionColumnUserID) + db.Timetravel(call.Took(ctx))).
|
2023-05-05 15:34:53 +00:00
|
|
|
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*Session, string, error) {
|
|
|
|
session := new(Session)
|
|
|
|
|
|
|
|
var (
|
|
|
|
userID sql.NullString
|
|
|
|
userCheckedAt sql.NullTime
|
|
|
|
loginName sql.NullString
|
|
|
|
displayName sql.NullString
|
2023-07-14 11:16:16 +00:00
|
|
|
userResourceOwner sql.NullString
|
2023-05-05 15:34:53 +00:00
|
|
|
passwordCheckedAt sql.NullTime
|
2023-06-21 14:06:18 +00:00
|
|
|
intentCheckedAt sql.NullTime
|
2023-06-07 15:28:42 +00:00
|
|
|
passkeyCheckedAt sql.NullTime
|
2023-05-05 15:34:53 +00:00
|
|
|
metadata database.Map[[]byte]
|
|
|
|
token sql.NullString
|
2023-06-27 12:36:07 +00:00
|
|
|
sessionDomain sql.NullString
|
2023-05-05 15:34:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
err := row.Scan(
|
|
|
|
&session.ID,
|
|
|
|
&session.CreationDate,
|
|
|
|
&session.ChangeDate,
|
|
|
|
&session.Sequence,
|
|
|
|
&session.State,
|
|
|
|
&session.ResourceOwner,
|
|
|
|
&session.Creator,
|
2023-06-27 12:36:07 +00:00
|
|
|
&sessionDomain,
|
2023-05-05 15:34:53 +00:00
|
|
|
&userID,
|
|
|
|
&userCheckedAt,
|
|
|
|
&loginName,
|
|
|
|
&displayName,
|
2023-07-14 11:16:16 +00:00
|
|
|
&userResourceOwner,
|
2023-05-05 15:34:53 +00:00
|
|
|
&passwordCheckedAt,
|
2023-06-21 14:06:18 +00:00
|
|
|
&intentCheckedAt,
|
2023-06-07 15:28:42 +00:00
|
|
|
&passkeyCheckedAt,
|
2023-05-05 15:34:53 +00:00
|
|
|
&metadata,
|
|
|
|
&token,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if errs.Is(err, sql.ErrNoRows) {
|
|
|
|
return nil, "", errors.ThrowNotFound(err, "QUERY-SFeaa", "Errors.Session.NotExisting")
|
|
|
|
}
|
|
|
|
return nil, "", errors.ThrowInternal(err, "QUERY-SAder", "Errors.Internal")
|
|
|
|
}
|
|
|
|
|
2023-06-27 12:36:07 +00:00
|
|
|
session.Domain = sessionDomain.String
|
2023-05-05 15:34:53 +00:00
|
|
|
session.UserFactor.UserID = userID.String
|
|
|
|
session.UserFactor.UserCheckedAt = userCheckedAt.Time
|
|
|
|
session.UserFactor.LoginName = loginName.String
|
|
|
|
session.UserFactor.DisplayName = displayName.String
|
2023-07-14 11:16:16 +00:00
|
|
|
session.UserFactor.ResourceOwner = userResourceOwner.String
|
2023-05-05 15:34:53 +00:00
|
|
|
session.PasswordFactor.PasswordCheckedAt = passwordCheckedAt.Time
|
2023-06-21 14:06:18 +00:00
|
|
|
session.IntentFactor.IntentCheckedAt = intentCheckedAt.Time
|
2023-06-07 15:28:42 +00:00
|
|
|
session.PasskeyFactor.PasskeyCheckedAt = passkeyCheckedAt.Time
|
2023-05-05 15:34:53 +00:00
|
|
|
session.Metadata = metadata
|
|
|
|
|
|
|
|
return session, token.String, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func prepareSessionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*Sessions, error)) {
|
|
|
|
return sq.Select(
|
|
|
|
SessionColumnID.identifier(),
|
|
|
|
SessionColumnCreationDate.identifier(),
|
|
|
|
SessionColumnChangeDate.identifier(),
|
|
|
|
SessionColumnSequence.identifier(),
|
|
|
|
SessionColumnState.identifier(),
|
|
|
|
SessionColumnResourceOwner.identifier(),
|
|
|
|
SessionColumnCreator.identifier(),
|
2023-06-27 12:36:07 +00:00
|
|
|
SessionColumnDomain.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnUserID.identifier(),
|
|
|
|
SessionColumnUserCheckedAt.identifier(),
|
|
|
|
LoginNameNameCol.identifier(),
|
|
|
|
HumanDisplayNameCol.identifier(),
|
2023-07-14 11:16:16 +00:00
|
|
|
UserResourceOwnerCol.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnPasswordCheckedAt.identifier(),
|
2023-06-21 14:06:18 +00:00
|
|
|
SessionColumnIntentCheckedAt.identifier(),
|
2023-06-07 15:28:42 +00:00
|
|
|
SessionColumnPasskeyCheckedAt.identifier(),
|
2023-05-05 15:34:53 +00:00
|
|
|
SessionColumnMetadata.identifier(),
|
|
|
|
countColumn.identifier(),
|
|
|
|
).From(sessionsTable.identifier()).
|
|
|
|
LeftJoin(join(LoginNameUserIDCol, SessionColumnUserID)).
|
2023-07-14 11:16:16 +00:00
|
|
|
LeftJoin(join(HumanUserIDCol, SessionColumnUserID)).
|
|
|
|
LeftJoin(join(UserIDCol, SessionColumnUserID) + db.Timetravel(call.Took(ctx))).
|
2023-05-05 15:34:53 +00:00
|
|
|
PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) (*Sessions, error) {
|
|
|
|
sessions := &Sessions{Sessions: []*Session{}}
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
session := new(Session)
|
|
|
|
|
|
|
|
var (
|
|
|
|
userID sql.NullString
|
|
|
|
userCheckedAt sql.NullTime
|
|
|
|
loginName sql.NullString
|
|
|
|
displayName sql.NullString
|
2023-07-14 11:16:16 +00:00
|
|
|
userResourceOwner sql.NullString
|
2023-05-05 15:34:53 +00:00
|
|
|
passwordCheckedAt sql.NullTime
|
2023-06-21 14:06:18 +00:00
|
|
|
intentCheckedAt sql.NullTime
|
2023-06-07 15:28:42 +00:00
|
|
|
passkeyCheckedAt sql.NullTime
|
2023-05-05 15:34:53 +00:00
|
|
|
metadata database.Map[[]byte]
|
2023-06-27 12:36:07 +00:00
|
|
|
sessionDomain sql.NullString
|
2023-05-05 15:34:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
err := rows.Scan(
|
|
|
|
&session.ID,
|
|
|
|
&session.CreationDate,
|
|
|
|
&session.ChangeDate,
|
|
|
|
&session.Sequence,
|
|
|
|
&session.State,
|
|
|
|
&session.ResourceOwner,
|
|
|
|
&session.Creator,
|
2023-06-27 12:36:07 +00:00
|
|
|
&sessionDomain,
|
2023-05-05 15:34:53 +00:00
|
|
|
&userID,
|
|
|
|
&userCheckedAt,
|
|
|
|
&loginName,
|
|
|
|
&displayName,
|
2023-07-14 11:16:16 +00:00
|
|
|
&userResourceOwner,
|
2023-05-05 15:34:53 +00:00
|
|
|
&passwordCheckedAt,
|
2023-06-21 14:06:18 +00:00
|
|
|
&intentCheckedAt,
|
2023-06-07 15:28:42 +00:00
|
|
|
&passkeyCheckedAt,
|
2023-05-05 15:34:53 +00:00
|
|
|
&metadata,
|
|
|
|
&sessions.Count,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.ThrowInternal(err, "QUERY-SAfeg", "Errors.Internal")
|
|
|
|
}
|
2023-06-27 12:36:07 +00:00
|
|
|
session.Domain = sessionDomain.String
|
2023-05-05 15:34:53 +00:00
|
|
|
session.UserFactor.UserID = userID.String
|
|
|
|
session.UserFactor.UserCheckedAt = userCheckedAt.Time
|
|
|
|
session.UserFactor.LoginName = loginName.String
|
|
|
|
session.UserFactor.DisplayName = displayName.String
|
2023-07-14 11:16:16 +00:00
|
|
|
session.UserFactor.ResourceOwner = userResourceOwner.String
|
2023-05-05 15:34:53 +00:00
|
|
|
session.PasswordFactor.PasswordCheckedAt = passwordCheckedAt.Time
|
2023-06-21 14:06:18 +00:00
|
|
|
session.IntentFactor.IntentCheckedAt = intentCheckedAt.Time
|
2023-06-07 15:28:42 +00:00
|
|
|
session.PasskeyFactor.PasskeyCheckedAt = passkeyCheckedAt.Time
|
2023-05-05 15:34:53 +00:00
|
|
|
session.Metadata = metadata
|
|
|
|
|
|
|
|
sessions.Sessions = append(sessions.Sessions, session)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sessions, nil
|
|
|
|
}
|
|
|
|
}
|