feat(oidc): id token for device authorization (#7088)

* cleanup todo

* pass id token details to oidc

* feat(oidc): id token for device authorization

This changes updates to the newest oidc version,
so the Device Authorization grant can return ID tokens when
the scope `openid` is set.
There is also some refactoring done, so that the eventstore can be
queried directly when polling for state.
The projection is cleaned up to a minimum with only data required for the login UI.

* try to be explicit wit hthe timezone to fix github

* pin oidc v3.8.0

* remove TBD entry
This commit is contained in:
Tim Möhlmann
2023-12-20 14:21:08 +02:00
committed by GitHub
parent e15f6229cd
commit e22689c125
25 changed files with 629 additions and 621 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"time"
sq "github.com/Masterminds/squirrel"
@@ -16,90 +17,80 @@ import (
)
var (
deviceAuthTable = table{
name: projection.DeviceAuthProjectionTable,
instanceIDCol: projection.DeviceAuthColumnInstanceID,
deviceAuthRequestTable = table{
name: projection.DeviceAuthRequestProjectionTable,
instanceIDCol: projection.DeviceAuthRequestColumnInstanceID,
}
DeviceAuthColumnID = Column{
name: projection.DeviceAuthColumnID,
table: deviceAuthTable,
DeviceAuthRequestColumnClientID = Column{
name: projection.DeviceAuthRequestColumnClientID,
table: deviceAuthRequestTable,
}
DeviceAuthColumnClientID = Column{
name: projection.DeviceAuthColumnClientID,
table: deviceAuthTable,
DeviceAuthRequestColumnDeviceCode = Column{
name: projection.DeviceAuthRequestColumnDeviceCode,
table: deviceAuthRequestTable,
}
DeviceAuthColumnDeviceCode = Column{
name: projection.DeviceAuthColumnDeviceCode,
table: deviceAuthTable,
DeviceAuthRequestColumnUserCode = Column{
name: projection.DeviceAuthRequestColumnUserCode,
table: deviceAuthRequestTable,
}
DeviceAuthColumnUserCode = Column{
name: projection.DeviceAuthColumnUserCode,
table: deviceAuthTable,
DeviceAuthRequestColumnScopes = Column{
name: projection.DeviceAuthRequestColumnScopes,
table: deviceAuthRequestTable,
}
DeviceAuthColumnExpires = Column{
name: projection.DeviceAuthColumnExpires,
table: deviceAuthTable,
DeviceAuthRequestColumnCreationDate = Column{
name: projection.DeviceAuthRequestColumnCreationDate,
table: deviceAuthRequestTable,
}
DeviceAuthColumnScopes = Column{
name: projection.DeviceAuthColumnScopes,
table: deviceAuthTable,
DeviceAuthRequestColumnChangeDate = Column{
name: projection.DeviceAuthRequestColumnChangeDate,
table: deviceAuthRequestTable,
}
DeviceAuthColumnState = Column{
name: projection.DeviceAuthColumnState,
table: deviceAuthTable,
DeviceAuthRequestColumnSequence = Column{
name: projection.DeviceAuthRequestColumnSequence,
table: deviceAuthRequestTable,
}
DeviceAuthColumnSubject = Column{
name: projection.DeviceAuthColumnSubject,
table: deviceAuthTable,
}
DeviceAuthColumnCreationDate = Column{
name: projection.DeviceAuthColumnCreationDate,
table: deviceAuthTable,
}
DeviceAuthColumnChangeDate = Column{
name: projection.DeviceAuthColumnChangeDate,
table: deviceAuthTable,
}
DeviceAuthColumnSequence = Column{
name: projection.DeviceAuthColumnSequence,
table: deviceAuthTable,
}
DeviceAuthColumnInstanceID = Column{
name: projection.DeviceAuthColumnInstanceID,
table: deviceAuthTable,
DeviceAuthRequestColumnInstanceID = Column{
name: projection.DeviceAuthRequestColumnInstanceID,
table: deviceAuthRequestTable,
}
)
func (q *Queries) DeviceAuthByDeviceCode(ctx context.Context, clientID, deviceCode string) (deviceAuth *domain.DeviceAuth, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
stmt, scan := prepareDeviceAuthQuery(ctx, q.client)
eq := sq.Eq{
DeviceAuthColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
DeviceAuthColumnClientID.identifier(): clientID,
DeviceAuthColumnDeviceCode.identifier(): deviceCode,
}
query, args, err := stmt.Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-uk1Oh", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
deviceAuth, err = scan(row)
return err
}, query, args...)
return deviceAuth, err
type DeviceAuth struct {
ClientID string
DeviceCode string
UserCode string
Expires time.Time
Scopes []string
State domain.DeviceAuthState
Subject string
UserAuthMethods []domain.UserAuthMethodType
AuthTime time.Time
}
func (q *Queries) DeviceAuthByUserCode(ctx context.Context, userCode string) (deviceAuth *domain.DeviceAuth, err error) {
// DeviceAuthByDeviceCode gets the current state of a Device Authorization directly from the eventstore.
func (q *Queries) DeviceAuthByDeviceCode(ctx context.Context, deviceCode string) (deviceAuth *DeviceAuth, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
model := NewDeviceAuthReadModel(deviceCode, authz.GetInstance(ctx).InstanceID())
if err := q.eventstore.FilterToQueryReducer(ctx, model); err != nil {
return nil, err
}
if !model.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "QUERY-eeR0e", "Errors.DeviceAuth.NotExisting")
}
return &model.DeviceAuth, nil
}
// DeviceAuthRequestByUserCode finds a Device Authorization request by User-Code from the `device_auth_requests` projection.
func (q *Queries) DeviceAuthRequestByUserCode(ctx context.Context, userCode string) (authReq *domain.AuthRequestDevice, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
stmt, scan := prepareDeviceAuthQuery(ctx, q.client)
eq := sq.Eq{
DeviceAuthColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
DeviceAuthColumnUserCode.identifier(): userCode,
DeviceAuthRequestColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
DeviceAuthRequestColumnUserCode.identifier(): userCode,
}
query, args, err := stmt.Where(eq).ToSql()
if err != nil {
@@ -107,34 +98,32 @@ func (q *Queries) DeviceAuthByUserCode(ctx context.Context, userCode string) (de
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
deviceAuth, err = scan(row)
authReq, err = scan(row)
return err
}, query, args...)
return deviceAuth, err
return authReq, err
}
var deviceAuthSelectColumns = []string{
DeviceAuthColumnID.identifier(),
DeviceAuthColumnClientID.identifier(),
DeviceAuthColumnScopes.identifier(),
DeviceAuthColumnExpires.identifier(),
DeviceAuthColumnState.identifier(),
DeviceAuthColumnSubject.identifier(),
DeviceAuthRequestColumnClientID.identifier(),
DeviceAuthRequestColumnDeviceCode.identifier(),
DeviceAuthRequestColumnUserCode.identifier(),
DeviceAuthRequestColumnScopes.identifier(),
}
func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*domain.DeviceAuth, error)) {
return sq.Select(deviceAuthSelectColumns...).From(deviceAuthTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*domain.DeviceAuth, error) {
dst := new(domain.DeviceAuth)
var scopes database.TextArray[string]
func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*domain.AuthRequestDevice, error)) {
return sq.Select(deviceAuthSelectColumns...).From(deviceAuthRequestTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*domain.AuthRequestDevice, error) {
dst := new(domain.AuthRequestDevice)
var (
scopes database.TextArray[string]
)
err := row.Scan(
&dst.AggregateID,
&dst.ClientID,
&dst.DeviceCode,
&dst.UserCode,
&scopes,
&dst.Expires,
&dst.State,
&dst.Subject,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(err, "QUERY-Sah9a", "Errors.DeviceAuth.NotExisting")
@@ -142,7 +131,6 @@ func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Voo3o", "Errors.Internal")
}
dst.Scopes = scopes
return dst, nil
}

View File

@@ -0,0 +1,58 @@
package query
import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/deviceauth"
)
type DeviceAuthReadModel struct {
eventstore.ReadModel
DeviceAuth
}
func NewDeviceAuthReadModel(deviceCode, resourceOwner string) *DeviceAuthReadModel {
return &DeviceAuthReadModel{
ReadModel: eventstore.ReadModel{
AggregateID: deviceCode,
ResourceOwner: resourceOwner,
},
}
}
func (m *DeviceAuthReadModel) Reduce() error {
for _, event := range m.Events {
switch e := event.(type) {
case *deviceauth.AddedEvent:
m.ClientID = e.ClientID
m.DeviceCode = e.DeviceCode
m.UserCode = e.UserCode
m.Expires = e.Expires
m.Scopes = e.Scopes
m.State = e.State
case *deviceauth.ApprovedEvent:
m.State = domain.DeviceAuthStateApproved
m.Subject = e.Subject
m.UserAuthMethods = e.UserAuthMethods
m.AuthTime = e.AuthTime
case *deviceauth.CanceledEvent:
m.State = e.Reason.State()
}
}
return m.ReadModel.Reduce()
}
func (m *DeviceAuthReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(m.ResourceOwner).
AddQuery().
AggregateTypes(deviceauth.AggregateType).
AggregateIDs(m.AggregateID).
EventTypes(
deviceauth.AddedEventType,
deviceauth.ApprovedEventType,
deviceauth.CanceledEventType,
).
Builder()
}

View File

@@ -6,82 +6,188 @@ import (
"database/sql/driver"
"errors"
"fmt"
"io"
"regexp"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
const (
expectedDeviceAuthQueryC = `SELECT` +
` projections.device_authorizations.id,` +
` projections.device_authorizations.client_id,` +
` projections.device_authorizations.scopes,` +
` projections.device_authorizations.expires,` +
` projections.device_authorizations.state,` +
` projections.device_authorizations.subject` +
` FROM projections.device_authorizations`
expectedDeviceAuthWhereDeviceCodeQueryC = expectedDeviceAuthQueryC +
` WHERE projections.device_authorizations.client_id = $1` +
` AND projections.device_authorizations.device_code = $2` +
` AND projections.device_authorizations.instance_id = $3`
expectedDeviceAuthWhereUserCodeQueryC = expectedDeviceAuthQueryC +
` WHERE projections.device_authorizations.instance_id = $1` +
` AND projections.device_authorizations.user_code = $2`
)
var (
expectedDeviceAuthQuery = regexp.QuoteMeta(expectedDeviceAuthQueryC)
expectedDeviceAuthWhereDeviceCodeQuery = regexp.QuoteMeta(expectedDeviceAuthWhereDeviceCodeQueryC)
expectedDeviceAuthWhereUserCodeQuery = regexp.QuoteMeta(expectedDeviceAuthWhereUserCodeQueryC)
expectedDeviceAuthValues = []driver.Value{
"primary-id",
"client-id",
database.TextArray[string]{"a", "b", "c"},
testNow,
domain.DeviceAuthStateApproved,
"subject",
}
expectedDeviceAuth = &domain.DeviceAuth{
ObjectRoot: models.ObjectRoot{
AggregateID: "primary-id",
},
ClientID: "client-id",
Scopes: []string{"a", "b", "c"},
Expires: testNow,
State: domain.DeviceAuthStateApproved,
Subject: "subject",
}
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/deviceauth"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestQueries_DeviceAuthByDeviceCode(t *testing.T) {
client, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to build mock client: %v", err)
ctx := authz.NewMockContext("inst1", "org1", "user1")
timestamp := time.Date(2015, 12, 15, 22, 13, 45, 0, time.UTC)
tests := []struct {
name string
eventstore func(t *testing.T) *eventstore.Eventstore
want *DeviceAuth
wantErr error
}{
{
name: "filter error",
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
wantErr: io.ErrClosedPipe,
},
{
name: "not found",
eventstore: expectEventstore(
expectFilter(),
),
wantErr: zerrors.ThrowNotFound(nil, "QUERY-eeR0e", "Errors.DeviceAuth.NotExisting"),
},
{
name: "ok, initiated",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
"client1", "device1", "user-code", timestamp, []string{"foo", "bar"},
)),
),
),
want: &DeviceAuth{
ClientID: "client1",
DeviceCode: "device1",
UserCode: "user-code",
Expires: timestamp,
Scopes: []string{"foo", "bar"},
State: domain.DeviceAuthStateInitiated,
},
},
{
name: "ok, approved",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
"client1", "device1", "user-code", timestamp, []string{"foo", "bar"},
)),
eventFromEventPusher(deviceauth.NewApprovedEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
"user1", []domain.UserAuthMethodType{domain.UserAuthMethodTypePasswordless},
timestamp,
)),
),
),
want: &DeviceAuth{
ClientID: "client1",
DeviceCode: "device1",
UserCode: "user-code",
Expires: timestamp,
Scopes: []string{"foo", "bar"},
State: domain.DeviceAuthStateApproved,
Subject: "user1",
UserAuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePasswordless},
AuthTime: timestamp,
},
},
{
name: "ok, denied",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
"client1", "device1", "user-code", timestamp, []string{"foo", "bar"},
)),
eventFromEventPusher(deviceauth.NewCanceledEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
domain.DeviceAuthCanceledDenied,
)),
),
),
want: &DeviceAuth{
ClientID: "client1",
DeviceCode: "device1",
UserCode: "user-code",
Expires: timestamp,
Scopes: []string{"foo", "bar"},
State: domain.DeviceAuthStateDenied,
},
},
{
name: "ok, expired",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
"client1", "device1", "user-code", timestamp, []string{"foo", "bar"},
)),
eventFromEventPusher(deviceauth.NewCanceledEvent(
ctx,
deviceauth.NewAggregate("device1", "instance1"),
domain.DeviceAuthCanceledExpired,
)),
),
),
want: &DeviceAuth{
ClientID: "client1",
DeviceCode: "device1",
UserCode: "user-code",
Expires: timestamp,
Scopes: []string{"foo", "bar"},
State: domain.DeviceAuthStateExpired,
},
},
}
defer client.Close()
mock.ExpectBegin()
mock.ExpectQuery(expectedDeviceAuthWhereDeviceCodeQuery).WillReturnRows(
sqlmock.NewRows(deviceAuthSelectColumns).AddRow(expectedDeviceAuthValues...),
)
mock.ExpectCommit()
q := Queries{
client: &database.DB{DB: client},
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := &Queries{
eventstore: tt.eventstore(t),
}
got, err := q.DeviceAuthByDeviceCode(ctx, "device1")
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})
}
got, err := q.DeviceAuthByDeviceCode(context.TODO(), "123", "456")
require.NoError(t, err)
assert.Equal(t, expectedDeviceAuth, got)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueries_DeviceAuthByUserCode(t *testing.T) {
const (
expectedDeviceAuthQueryC = `SELECT` +
` projections.device_auth_requests.client_id,` +
` projections.device_auth_requests.device_code,` +
` projections.device_auth_requests.user_code,` +
` projections.device_auth_requests.scopes` +
` FROM projections.device_auth_requests`
expectedDeviceAuthWhereUserCodeQueryC = expectedDeviceAuthQueryC +
` WHERE projections.device_auth_requests.instance_id = $1` +
` AND projections.device_auth_requests.user_code = $2`
)
var (
expectedDeviceAuthQuery = regexp.QuoteMeta(expectedDeviceAuthQueryC)
expectedDeviceAuthWhereUserCodeQuery = regexp.QuoteMeta(expectedDeviceAuthWhereUserCodeQueryC)
expectedDeviceAuthValues = []driver.Value{
"client-id",
"device1",
"user-code",
database.TextArray[string]{"a", "b", "c"},
}
expectedDeviceAuth = &domain.AuthRequestDevice{
ClientID: "client-id",
DeviceCode: "device1",
UserCode: "user-code",
Scopes: []string{"a", "b", "c"},
}
)
func TestQueries_DeviceAuthRequestByUserCode(t *testing.T) {
client, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to build mock client: %v", err)
@@ -96,7 +202,7 @@ func TestQueries_DeviceAuthByUserCode(t *testing.T) {
q := Queries{
client: &database.DB{DB: client},
}
got, err := q.DeviceAuthByUserCode(context.TODO(), "789")
got, err := q.DeviceAuthRequestByUserCode(context.TODO(), "789")
require.NoError(t, err)
assert.Equal(t, expectedDeviceAuth, got)
require.NoError(t, mock.ExpectationsWereMet())
@@ -110,7 +216,7 @@ func Test_prepareDeviceAuthQuery(t *testing.T) {
tests := []struct {
name string
want want
object any
object *domain.AuthRequestDevice
}{
{
name: "success",
@@ -137,7 +243,7 @@ func Test_prepareDeviceAuthQuery(t *testing.T) {
return nil, true
},
},
object: (*domain.DeviceAuth)(nil),
object: nil,
},
{
name: "other error",
@@ -153,7 +259,7 @@ func Test_prepareDeviceAuthQuery(t *testing.T) {
return nil, true
},
},
object: (*domain.DeviceAuth)(nil),
object: nil,
},
}
for _, tt := range tests {

View File

@@ -3,7 +3,6 @@ package projection
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
@@ -12,57 +11,51 @@ import (
)
const (
DeviceAuthProjectionTable = "projections.device_authorizations"
DeviceAuthRequestProjectionTable = "projections.device_auth_requests"
DeviceAuthColumnID = "id"
DeviceAuthColumnClientID = "client_id"
DeviceAuthColumnDeviceCode = "device_code"
DeviceAuthColumnUserCode = "user_code"
DeviceAuthColumnExpires = "expires"
DeviceAuthColumnScopes = "scopes"
DeviceAuthColumnState = "state"
DeviceAuthColumnSubject = "subject"
DeviceAuthColumnCreationDate = "creation_date"
DeviceAuthColumnChangeDate = "change_date"
DeviceAuthColumnSequence = "sequence"
DeviceAuthColumnInstanceID = "instance_id"
DeviceAuthRequestColumnClientID = "client_id"
DeviceAuthRequestColumnDeviceCode = "device_code"
DeviceAuthRequestColumnUserCode = "user_code"
DeviceAuthRequestColumnScopes = "scopes"
DeviceAuthRequestColumnCreationDate = "creation_date"
DeviceAuthRequestColumnChangeDate = "change_date"
DeviceAuthRequestColumnSequence = "sequence"
DeviceAuthRequestColumnInstanceID = "instance_id"
)
type deviceAuthProjection struct{}
// deviceAuthRequestProjection holds device authorization requests
// and makes them search-able by User Code.
// In principle the projected data is only needed during user login.
// Device Token logic uses the eventstore directly.
type deviceAuthRequestProjection struct{}
func newDeviceAuthProjection(ctx context.Context, config handler.Config) *handler.Handler {
return handler.NewHandler(ctx, &config, new(deviceAuthProjection))
return handler.NewHandler(ctx, &config, new(deviceAuthRequestProjection))
}
func (*deviceAuthProjection) Name() string {
return DeviceAuthProjectionTable
func (*deviceAuthRequestProjection) Name() string {
return DeviceAuthRequestProjectionTable
}
func (*deviceAuthProjection) Init() *old_handler.Check {
func (*deviceAuthRequestProjection) Init() *old_handler.Check {
return handler.NewTableCheck(
handler.NewTable([]*handler.InitColumn{
handler.NewColumn(DeviceAuthColumnID, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthColumnClientID, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthColumnDeviceCode, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthColumnUserCode, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthColumnExpires, handler.ColumnTypeTimestamp),
handler.NewColumn(DeviceAuthColumnScopes, handler.ColumnTypeTextArray),
handler.NewColumn(DeviceAuthColumnState, handler.ColumnTypeEnum, handler.Default(domain.DeviceAuthStateInitiated)),
handler.NewColumn(DeviceAuthColumnSubject, handler.ColumnTypeText, handler.Default("")),
handler.NewColumn(DeviceAuthColumnCreationDate, handler.ColumnTypeTimestamp),
handler.NewColumn(DeviceAuthColumnChangeDate, handler.ColumnTypeTimestamp),
handler.NewColumn(DeviceAuthColumnSequence, handler.ColumnTypeInt64),
handler.NewColumn(DeviceAuthColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthRequestColumnClientID, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthRequestColumnDeviceCode, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthRequestColumnUserCode, handler.ColumnTypeText),
handler.NewColumn(DeviceAuthRequestColumnScopes, handler.ColumnTypeTextArray),
handler.NewColumn(DeviceAuthRequestColumnCreationDate, handler.ColumnTypeTimestamp),
handler.NewColumn(DeviceAuthRequestColumnChangeDate, handler.ColumnTypeTimestamp),
handler.NewColumn(DeviceAuthRequestColumnSequence, handler.ColumnTypeInt64),
handler.NewColumn(DeviceAuthRequestColumnInstanceID, handler.ColumnTypeText),
},
handler.NewPrimaryKey(DeviceAuthColumnInstanceID, DeviceAuthColumnID),
handler.WithIndex(handler.NewIndex("user_code", []string{DeviceAuthColumnInstanceID, DeviceAuthColumnUserCode})),
handler.WithIndex(handler.NewIndex("device_code", []string{DeviceAuthColumnInstanceID, DeviceAuthColumnClientID, DeviceAuthColumnDeviceCode})),
handler.NewPrimaryKey(DeviceAuthRequestColumnInstanceID, DeviceAuthRequestColumnDeviceCode),
handler.WithIndex(handler.NewIndex("user_code", []string{DeviceAuthRequestColumnInstanceID, DeviceAuthRequestColumnUserCode})),
),
)
}
func (p *deviceAuthProjection) Reducers() []handler.AggregateReducer {
func (p *deviceAuthRequestProjection) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: deviceauth.AggregateType,
@@ -73,22 +66,18 @@ func (p *deviceAuthProjection) Reducers() []handler.AggregateReducer {
},
{
Event: deviceauth.ApprovedEventType,
Reduce: p.reduceAppoved,
Reduce: p.reduceDoneEvents,
},
{
Event: deviceauth.CanceledEventType,
Reduce: p.reduceCanceled,
},
{
Event: deviceauth.RemovedEventType,
Reduce: p.reduceRemoved,
Reduce: p.reduceDoneEvents,
},
},
},
}
}
func (p *deviceAuthProjection) reduceAdded(event eventstore.Event) (*handler.Statement, error) {
func (p *deviceAuthRequestProjection) reduceAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*deviceauth.AddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-chu6O", "reduce.wrong.event.type %T != %s", event, deviceauth.AddedEventType)
@@ -96,66 +85,30 @@ func (p *deviceAuthProjection) reduceAdded(event eventstore.Event) (*handler.Sta
return handler.NewCreateStatement(
e,
[]handler.Column{
handler.NewCol(DeviceAuthColumnID, e.Aggregate().ID),
handler.NewCol(DeviceAuthColumnClientID, e.ClientID),
handler.NewCol(DeviceAuthColumnDeviceCode, e.DeviceCode),
handler.NewCol(DeviceAuthColumnUserCode, e.UserCode),
handler.NewCol(DeviceAuthColumnExpires, e.Expires),
handler.NewCol(DeviceAuthColumnScopes, e.Scopes),
handler.NewCol(DeviceAuthColumnCreationDate, e.CreationDate()),
handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
handler.NewCol(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(DeviceAuthRequestColumnClientID, e.ClientID),
handler.NewCol(DeviceAuthRequestColumnDeviceCode, e.DeviceCode),
handler.NewCol(DeviceAuthRequestColumnUserCode, e.UserCode),
handler.NewCol(DeviceAuthRequestColumnScopes, e.Scopes),
handler.NewCol(DeviceAuthRequestColumnCreationDate, e.CreationDate()),
handler.NewCol(DeviceAuthRequestColumnChangeDate, e.CreationDate()),
handler.NewCol(DeviceAuthRequestColumnSequence, e.Sequence()),
handler.NewCol(DeviceAuthRequestColumnInstanceID, e.Aggregate().InstanceID),
},
), nil
}
func (p *deviceAuthProjection) reduceAppoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*deviceauth.ApprovedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-kei0A", "reduce.wrong.event.type %T != %s", event, deviceauth.ApprovedEventType)
}
return handler.NewUpdateStatement(e,
[]handler.Column{
handler.NewCol(DeviceAuthColumnState, domain.DeviceAuthStateApproved),
handler.NewCol(DeviceAuthColumnSubject, e.Subject),
handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
},
), nil
}
// reduceDoneEvents removes the device auth request from the projection.
func (p *deviceAuthRequestProjection) reduceDoneEvents(event eventstore.Event) (*handler.Statement, error) {
switch event.(type) {
case *deviceauth.ApprovedEvent, *deviceauth.CanceledEvent:
return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(DeviceAuthRequestColumnInstanceID, event.Aggregate().InstanceID),
handler.NewCond(DeviceAuthRequestColumnDeviceCode, event.Aggregate().ID),
},
), nil
func (p *deviceAuthProjection) reduceCanceled(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*deviceauth.CanceledEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-eeS8d", "reduce.wrong.event.type %T != %s", event, deviceauth.CanceledEventType)
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-eeS8d", "reduce.wrong.event.type %T", event)
}
return handler.NewUpdateStatement(e,
[]handler.Column{
handler.NewCol(DeviceAuthColumnState, e.Reason.State()),
handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
},
), nil
}
func (p *deviceAuthProjection) reduceRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*deviceauth.RemovedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-AJi1u", "reduce.wrong.event.type %T != %s", event, deviceauth.RemovedEventType)
}
return handler.NewDeleteStatement(e,
[]handler.Condition{
handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
},
), nil
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/action"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/repository/deviceauth"
"github.com/zitadel/zitadel/internal/repository/idpintent"
iam_repo "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/keypair"
@@ -99,6 +100,7 @@ func StartQueries(
quota.RegisterEventMappers(repo.eventstore)
limits.RegisterEventMappers(repo.eventstore)
restrictions.RegisterEventMappers(repo.eventstore)
deviceauth.RegisterEventMappers(repo.eventstore)
repo.checkPermission = permissionCheck(repo)

View File

@@ -12,6 +12,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore/repository/mock"
action_repo "github.com/zitadel/zitadel/internal/repository/action"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/repository/deviceauth"
"github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/idpintent"
iam_repo "github.com/zitadel/zitadel/internal/repository/instance"
@@ -54,6 +55,7 @@ func expectEventstore(expects ...expect) func(*testing.T) *eventstore.Eventstore
quota_repo.RegisterEventMappers(es)
limits.RegisterEventMappers(es)
feature.RegisterEventMappers(es)
deviceauth.RegisterEventMappers(es)
return es
}
}