mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 21:08:00 +00:00
9ec9ad4314
# Which Problems Are Solved id_tokens issued for auth requests created through the login UI currently do not provide a sid claim. This is due to the fact that (SSO) sessions for the login UI do not have one and are only computed by the userAgent(ID), the user(ID) and the authentication checks of the latter. This prevents client to track sessions and terminate specific session on the end_session_endpoint. # How the Problems Are Solved - An `id` column is added to the `auth.user_sessions` table. - The `id` (prefixed with `V1_`) is set whenever a session is added or updated to active (from terminated) - The id is passed to the `oidc session` (as v2 sessionIDs), to expose it as `sid` claim # Additional Changes - refactored `getUpdateCols` to handle different column value types and add arguments for query # Additional Context - closes #8499 - relates to #8501
181 lines
5.7 KiB
Go
181 lines
5.7 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/deviceauth"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes, audience []string, needRefreshToken bool) (*domain.ObjectDetails, error) {
|
|
aggr := deviceauth.NewAggregate(deviceCode, authz.GetInstance(ctx).InstanceID())
|
|
model := NewDeviceAuthWriteModel(deviceCode, aggr.ResourceOwner)
|
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewAddedEvent(
|
|
ctx,
|
|
aggr,
|
|
clientID,
|
|
deviceCode,
|
|
userCode,
|
|
expires,
|
|
scopes,
|
|
audience,
|
|
needRefreshToken,
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(model, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return writeModelToObjectDetails(&model.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) ApproveDeviceAuth(
|
|
ctx context.Context,
|
|
deviceCode,
|
|
userID,
|
|
userOrgID string,
|
|
authMethods []domain.UserAuthMethodType,
|
|
authTime time.Time,
|
|
preferredLanguage *language.Tag,
|
|
userAgent *domain.UserAgent,
|
|
sessionID string,
|
|
) (*domain.ObjectDetails, error) {
|
|
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !model.State.Exists() {
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound")
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent, sessionID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(model, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return writeModelToObjectDetails(&model.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) CancelDeviceAuth(ctx context.Context, id string, reason domain.DeviceAuthCanceled) (*domain.ObjectDetails, error) {
|
|
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !model.State.Exists() {
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-gee5A", "Errors.DeviceAuth.NotFound")
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewCanceledEvent(ctx, model.aggregate, reason))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(model, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return writeModelToObjectDetails(&model.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) getDeviceAuthWriteModelByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuthWriteModel, error) {
|
|
model := &DeviceAuthWriteModel{
|
|
WriteModel: eventstore.WriteModel{AggregateID: deviceCode},
|
|
}
|
|
err := c.eventstore.FilterToQueryReducer(ctx, model)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
model.aggregate = deviceauth.NewAggregate(model.AggregateID, model.InstanceID)
|
|
return model, nil
|
|
}
|
|
|
|
type DeviceAuthStateError domain.DeviceAuthState
|
|
|
|
func (e DeviceAuthStateError) Error() string {
|
|
return fmt.Sprintf("device auth state not approved: %s", domain.DeviceAuthState(e).String())
|
|
}
|
|
|
|
// CreateOIDCSessionFromDeviceAuth creates a new OIDC session if the device authorization
|
|
// flow is completed (user logged in).
|
|
// A [DeviceAuthStateError] is returned if the device authorization was not approved,
|
|
// containing a [domain.DeviceAuthState] which can be used to inform the client about the state.
|
|
//
|
|
// As devices can poll at various intervals, an explicit state takes precedence over expiry.
|
|
// This is to prevent cases where users might approve or deny the authorization on time, but the next poll
|
|
// happens after expiry.
|
|
func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCode string) (_ *OIDCSession, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
deviceAuthModel, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch deviceAuthModel.State {
|
|
case domain.DeviceAuthStateApproved:
|
|
break
|
|
case domain.DeviceAuthStateUndefined:
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-ua1Vo", "Errors.DeviceAuth.NotFound")
|
|
|
|
case domain.DeviceAuthStateInitiated:
|
|
if deviceAuthModel.Expires.Before(time.Now()) {
|
|
c.asyncPush(ctx, deviceauth.NewCanceledEvent(ctx, deviceAuthModel.aggregate, domain.DeviceAuthCanceledExpired))
|
|
return nil, DeviceAuthStateError(domain.DeviceAuthStateExpired)
|
|
}
|
|
fallthrough
|
|
case domain.DeviceAuthStateDenied, domain.DeviceAuthStateExpired, domain.DeviceAuthStateDone:
|
|
fallthrough
|
|
default:
|
|
return nil, DeviceAuthStateError(deviceAuthModel.State)
|
|
}
|
|
|
|
cmd, err := c.newOIDCSessionAddEvents(ctx, deviceAuthModel.UserOrgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd.AddSession(ctx,
|
|
deviceAuthModel.UserID,
|
|
deviceAuthModel.UserOrgID,
|
|
deviceAuthModel.SessionID,
|
|
deviceAuthModel.ClientID,
|
|
deviceAuthModel.Audience,
|
|
deviceAuthModel.Scopes,
|
|
deviceAuthModel.UserAuthMethods,
|
|
deviceAuthModel.AuthTime,
|
|
"",
|
|
deviceAuthModel.PreferredLanguage,
|
|
deviceAuthModel.UserAgent,
|
|
)
|
|
if err = cmd.AddAccessToken(ctx, deviceAuthModel.Scopes, deviceAuthModel.UserID, deviceAuthModel.UserOrgID, domain.TokenReasonAuthRequest, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if deviceAuthModel.NeedRefreshToken {
|
|
if err = cmd.AddRefreshToken(ctx, deviceAuthModel.UserID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
cmd.DeviceAuthRequestDone(ctx, deviceAuthModel.aggregate)
|
|
return cmd.PushEvents(ctx)
|
|
}
|
|
|
|
func (cmd *OIDCSessionEvents) DeviceAuthRequestDone(ctx context.Context, deviceAuthAggregate *eventstore.Aggregate) {
|
|
cmd.events = append(cmd.events, deviceauth.NewDoneEvent(ctx, deviceAuthAggregate))
|
|
}
|